Remove availability statement (#79)

* remove availability vote type from statement-table

* expunge availability statement from consensus module

* expunge availability from duty roster

* rename StatementProducer to ParachainWork

* fix runtime tests and remove availability statement variant

* update wasm
This commit is contained in:
Robert Habermeier
2019-01-18 07:45:05 -03:00
committed by Gav Wood
parent 7ac1fe9c01
commit 32cef97681
12 changed files with 202 additions and 620 deletions
+3 -6
View File
@@ -100,10 +100,7 @@ impl<C: Collators, P: ProvideRuntimeApi> Future for CollationFetch<C, P>
}; };
match validate_collation(&*self.client, &self.relay_parent, &x) { match validate_collation(&*self.client, &self.relay_parent, &x) {
Ok(()) => { Ok(e) => return Ok(Async::Ready((x, e))),
// TODO: generate extrinsic while verifying.
return Ok(Async::Ready((x, Extrinsic)));
}
Err(e) => { Err(e) => {
debug!("Failed to validate parachain due to API error: {}", e); debug!("Failed to validate parachain due to API error: {}", e);
@@ -145,7 +142,7 @@ pub fn validate_collation<P>(
client: &P, client: &P,
relay_parent: &BlockId, relay_parent: &BlockId,
collation: &Collation collation: &Collation
) -> Result<(), Error> where ) -> Result<Extrinsic, Error> where
P: ProvideRuntimeApi, P: ProvideRuntimeApi,
P::Api: ParachainHost<Block> P::Api: ParachainHost<Block>
{ {
@@ -167,7 +164,7 @@ pub fn validate_collation<P>(
match parachain::wasm::validate_candidate(&validation_code, params) { match parachain::wasm::validate_candidate(&validation_code, params) {
Ok(result) => { Ok(result) => {
if result.head_data == collation.receipt.head_data.0 { if result.head_data == collation.receipt.head_data.0 {
Ok(()) Ok(Extrinsic)
} else { } else {
Err(ErrorKind::WrongHeadData( Err(ErrorKind::WrongHeadData(
collation.receipt.head_data.0.clone(), collation.receipt.head_data.0.clone(),
+8 -25
View File
@@ -92,7 +92,7 @@ use dynamic_inclusion::DynamicInclusion;
pub use self::collation::{validate_collation, Collators}; pub use self::collation::{validate_collation, Collators};
pub use self::error::{ErrorKind, Error}; pub use self::error::{ErrorKind, Error};
pub use self::shared_table::{SharedTable, StatementProducer, ProducedStatements, Statement, SignedStatement, GenericStatement}; pub use self::shared_table::{SharedTable, ParachainWork, PrimedParachainWork, Validated, Statement, SignedStatement, GenericStatement};
mod attestation_service; mod attestation_service;
mod dynamic_inclusion; mod dynamic_inclusion;
@@ -147,12 +147,8 @@ pub trait Network {
pub struct GroupInfo { pub struct GroupInfo {
/// Authorities meant to check validity of candidates. /// Authorities meant to check validity of candidates.
pub validity_guarantors: HashSet<SessionKey>, pub validity_guarantors: HashSet<SessionKey>,
/// Authorities meant to check availability of candidate data.
pub availability_guarantors: HashSet<SessionKey>,
/// Number of votes needed for validity. /// Number of votes needed for validity.
pub needed_validity: usize, pub needed_validity: usize,
/// Number of votes needed for availability.
pub needed_availability: usize,
} }
/// Sign a table statement against a parent hash. /// Sign a table statement against a parent hash.
@@ -183,15 +179,11 @@ fn make_group_info(roster: DutyRoster, authorities: &[AuthorityId], local_id: Au
bail!(ErrorKind::InvalidDutyRosterLength(authorities.len(), roster.validator_duty.len())) bail!(ErrorKind::InvalidDutyRosterLength(authorities.len(), roster.validator_duty.len()))
} }
if roster.guarantor_duty.len() != authorities.len() {
bail!(ErrorKind::InvalidDutyRosterLength(authorities.len(), roster.guarantor_duty.len()))
}
let mut local_validation = None; let mut local_validation = None;
let mut map = HashMap::new(); let mut map = HashMap::new();
let duty_iter = authorities.iter().zip(&roster.validator_duty).zip(&roster.guarantor_duty); let duty_iter = authorities.iter().zip(&roster.validator_duty);
for ((authority, v_duty), a_duty) in duty_iter { for (authority, v_duty) in duty_iter {
if authority == &local_id { if authority == &local_id {
local_validation = Some(v_duty.clone()); local_validation = Some(v_duty.clone());
} }
@@ -204,23 +196,11 @@ fn make_group_info(roster: DutyRoster, authorities: &[AuthorityId], local_id: Au
.insert(authority.clone()); .insert(authority.clone());
} }
} }
match *a_duty {
Chain::Relay => {}, // does nothing for now.
Chain::Parachain(ref id) => {
map.entry(id.clone()).or_insert_with(GroupInfo::default)
.availability_guarantors
.insert(authority.clone());
}
}
} }
for live_group in map.values_mut() { for live_group in map.values_mut() {
let validity_len = live_group.validity_guarantors.len(); let validity_len = live_group.validity_guarantors.len();
let availability_len = live_group.availability_guarantors.len();
live_group.needed_validity = validity_len / 2 + validity_len % 2; live_group.needed_validity = validity_len / 2 + validity_len % 2;
live_group.needed_availability = availability_len / 2 + availability_len % 2;
} }
match local_validation { match local_validation {
@@ -470,8 +450,11 @@ fn dispatch_collation_work<R, C, P>(
}); });
match res { match res {
Ok(()) => Ok(()) => {
router.local_candidate(collation.receipt, collation.block_data, extrinsic), // TODO: https://github.com/paritytech/polkadot/issues/51
// Erasure-code and provide merkle branches.
router.local_candidate(collation.receipt, collation.block_data, extrinsic)
}
Err(e) => Err(e) =>
warn!(target: "consensus", "Failed to make collation data available: {:?}", e), warn!(target: "consensus", "Failed to make collation data available: {:?}", e),
} }
+109 -197
View File
@@ -22,18 +22,19 @@ use std::sync::Arc;
use extrinsic_store::{Data, Store as ExtrinsicStore}; use extrinsic_store::{Data, Store as ExtrinsicStore};
use table::{self, Table, Context as TableContextTrait}; use table::{self, Table, Context as TableContextTrait};
use polkadot_primitives::{Hash, SessionKey}; use polkadot_primitives::{Block, BlockId, Hash, SessionKey};
use polkadot_primitives::parachain::{ use polkadot_primitives::parachain::{
Id as ParaId, BlockData, Collation, Extrinsic, CandidateReceipt, Id as ParaId, BlockData, Collation, Extrinsic, CandidateReceipt,
AttestedCandidate, AttestedCandidate, ParachainHost
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use futures::{future, prelude::*}; use futures::prelude::*;
use super::{GroupInfo, TableRouter}; use super::{GroupInfo, TableRouter};
use self::includable::IncludabilitySender; use self::includable::IncludabilitySender;
use primitives::ed25519; use primitives::ed25519;
use runtime_primitives::{traits::ProvideRuntimeApi};
mod includable; mod includable;
@@ -52,15 +53,8 @@ impl table::Context for TableContext {
self.groups.get(group).map_or(false, |g| g.validity_guarantors.contains(authority)) self.groups.get(group).map_or(false, |g| g.validity_guarantors.contains(authority))
} }
fn is_availability_guarantor_of(&self, authority: &SessionKey, group: &ParaId) -> bool { fn requisite_votes(&self, group: &ParaId) -> usize {
self.groups.get(group).map_or(false, |g| g.availability_guarantors.contains(authority)) self.groups.get(group).map_or(usize::max_value(), |g| g.needed_validity)
}
fn requisite_votes(&self, group: &ParaId) -> (usize, usize) {
self.groups.get(group).map_or(
(usize::max_value(), usize::max_value()),
|g| (g.needed_validity, g.needed_availability),
)
} }
} }
@@ -85,7 +79,6 @@ struct SharedTableInner {
table: Table<TableContext>, table: Table<TableContext>,
proposed_digest: Option<Hash>, proposed_digest: Option<Hash>,
checked_validity: HashSet<Hash>, checked_validity: HashSet<Hash>,
checked_availability: HashSet<Hash>,
trackers: Vec<IncludabilitySender>, trackers: Vec<IncludabilitySender>,
extrinsic_store: ExtrinsicStore, extrinsic_store: ExtrinsicStore,
} }
@@ -101,9 +94,8 @@ impl SharedTableInner {
context: &TableContext, context: &TableContext,
router: &R, router: &R,
statement: table::SignedStatement, statement: table::SignedStatement,
) -> Option<StatementProducer< ) -> Option<ParachainWork<
<R::FetchCandidate as IntoFuture>::Future, <R::FetchCandidate as IntoFuture>::Future,
<R::FetchExtrinsic as IntoFuture>::Future,
>> { >> {
let summary = match self.table.import_statement(context, statement) { let summary = match self.table.import_statement(context, statement) {
Some(summary) => summary, Some(summary) => summary,
@@ -114,42 +106,25 @@ impl SharedTableInner {
let local_id = context.local_id(); let local_id = context.local_id();
let is_validity_member = context.is_member_of(&local_id, &summary.group_id); let para_member = context.is_member_of(&local_id, &summary.group_id);
let is_availability_member =
context.is_availability_guarantor_of(&local_id, &summary.group_id);
let digest = &summary.candidate; let digest = &summary.candidate;
// TODO: consider a strategy based on the number of candidate votes as well. // TODO: consider a strategy based on the number of candidate votes as well.
// only check validity if this wasn't locally proposed. // only check validity if this wasn't locally proposed.
let checking_validity = is_validity_member let extra_work = para_member
&& self.proposed_digest.as_ref().map_or(true, |d| d != digest) && self.proposed_digest.as_ref().map_or(true, |d| d != digest)
&& self.checked_validity.insert(digest.clone()); && self.checked_validity.insert(digest.clone());
let checking_availability = is_availability_member let work = if extra_work {
&& self.checked_availability.insert(digest.clone());
let work = if checking_validity || checking_availability {
match self.table.get_candidate(&digest) { match self.table.get_candidate(&digest) {
None => None, // TODO: handle table inconsistency somehow? None => None, // TODO: handle table inconsistency somehow?
Some(candidate) => { Some(candidate) => {
let fetch_block_data = let fetch_block_data = router.fetch_block_data(candidate).into_future();
router.fetch_block_data(candidate).into_future().fuse();
let fetch_extrinsic = if checking_availability {
Some(
router.fetch_extrinsic_data(candidate).into_future().fuse()
)
} else {
None
};
Some(Work { Some(Work {
candidate_receipt: candidate.clone(), candidate_receipt: candidate.clone(),
fetch_block_data, fetch_block_data,
fetch_extrinsic,
evaluate: checking_validity,
ensure_available: checking_availability,
}) })
} }
} }
@@ -157,8 +132,7 @@ impl SharedTableInner {
None None
}; };
work.map(|work| StatementProducer { work.map(|work| ParachainWork {
produced_statements: Default::default(),
extrinsic_store: self.extrinsic_store.clone(), extrinsic_store: self.extrinsic_store.clone(),
relay_parent: context.parent_hash.clone(), relay_parent: context.parent_hash.clone(),
work work
@@ -175,130 +149,114 @@ impl SharedTableInner {
} }
} }
/// Produced statements about a specific candidate. /// Produced after validating a candidate.
/// Both may be `None`. pub struct Validated {
#[derive(Default)]
pub struct ProducedStatements {
/// A statement about the validity of the candidate. /// A statement about the validity of the candidate.
pub validity: Option<table::Statement>, pub validity: table::Statement,
/// A statement about availability of data. If this is `Some`,
/// then `block_data` and `extrinsic` should be `Some` as well.
pub availability: Option<table::Statement>,
/// Block data to ensure availability of. /// Block data to ensure availability of.
pub block_data: Option<BlockData>, pub block_data: BlockData,
/// Extrinsic data to ensure availability of. /// Extrinsic data to ensure availability of.
pub extrinsic: Option<Extrinsic>, pub extrinsic: Extrinsic,
} }
/// Future that produces statements about a specific candidate. /// Future that performs parachain validation work.
pub struct StatementProducer<D: Future, E: Future> { pub struct ParachainWork<D: Future> {
produced_statements: ProducedStatements, work: Work<D>,
work: Work<D, E>,
relay_parent: Hash, relay_parent: Hash,
extrinsic_store: ExtrinsicStore, extrinsic_store: ExtrinsicStore,
} }
impl<D: Future, E: Future> StatementProducer<D, E> { impl<D: Future> ParachainWork<D> {
/// Attach a function for verifying fetched collation to the statement producer. /// Prime the parachain work with an API reference for extracting
/// This will transform it into a future. /// chain information.
/// pub fn prime<P: ProvideRuntimeApi>(self, api: Arc<P>)
/// The collation-checking function should return `true` if known to be valid, -> PrimedParachainWork<
/// `false` if known to be invalid, and `None` if unable to determine. D,
pub fn prime<C: FnMut(Collation) -> Option<bool>>(self, check_candidate: C) -> PrimedStatementProducer<D, E, C> { impl Send + FnMut(&BlockId, &Collation) -> bool,
PrimedStatementProducer { >
inner: self, where
check_candidate, P: Send + Sync + 'static,
P::Api: ParachainHost<Block>,
{
let validate = move |id: &_, collation: &_| {
let res = ::collation::validate_collation(
&*api,
id,
collation,
);
match res {
Ok(_) => true,
Err(e) => {
debug!(target: "consensus", "Encountered bad collation: {}", e);
false
} }
} }
};
PrimedParachainWork { inner: self, validate }
}
/// Prime the parachain work with a custom validation function.
pub fn prime_with<F>(self, validate: F) -> PrimedParachainWork<D, F>
where F: FnMut(&BlockId, &Collation) -> bool
{
PrimedParachainWork { inner: self, validate }
}
} }
struct Work<D: Future, E: Future> { struct Work<D: Future> {
candidate_receipt: CandidateReceipt, candidate_receipt: CandidateReceipt,
fetch_block_data: future::Fuse<D>, fetch_block_data: D,
fetch_extrinsic: Option<future::Fuse<E>>,
evaluate: bool,
ensure_available: bool,
} }
/// Primed statement producer. /// Primed statement producer.
pub struct PrimedStatementProducer<D: Future, E: Future, C> { pub struct PrimedParachainWork<D: Future, F> {
inner: StatementProducer<D, E>, inner: ParachainWork<D>,
check_candidate: C, validate: F,
} }
impl<D, E, C, Err> Future for PrimedStatementProducer<D, E, C> impl<D, F, Err> Future for PrimedParachainWork<D, F>
where where
D: Future<Item=BlockData,Error=Err>, D: Future<Item=BlockData,Error=Err>,
E: Future<Item=Extrinsic,Error=Err>, F: FnMut(&BlockId, &Collation) -> bool,
C: FnMut(Collation) -> Option<bool>,
Err: From<::std::io::Error>, Err: From<::std::io::Error>,
{ {
type Item = ProducedStatements; type Item = Validated;
type Error = Err; type Error = Err;
fn poll(&mut self) -> Poll<ProducedStatements, Err> { fn poll(&mut self) -> Poll<Validated, Err> {
let work = &mut self.inner.work; let work = &mut self.inner.work;
let candidate = &work.candidate_receipt; let candidate = &work.candidate_receipt;
let statements = &mut self.inner.produced_statements;
let mut candidate_hash = None; let block = try_ready!(work.fetch_block_data.poll());
let mut candidate_hash = move || let is_good = (self.validate)(
candidate_hash.get_or_insert_with(|| candidate.hash()).clone(); &BlockId::hash(self.inner.relay_parent),
&Collation { block_data: block.clone(), receipt: candidate.clone() },
);
if let Async::Ready(block_data) = work.fetch_block_data.poll()? { let candidate_hash = candidate.hash();
statements.block_data = Some(block_data.clone());
if work.evaluate {
let is_good = (self.check_candidate)(Collation {
block_data,
receipt: work.candidate_receipt.clone(),
});
let hash = candidate_hash(); debug!(target: "consensus", "Making validity statement about candidate {}: is_good? {:?}", candidate_hash, is_good);
let validity_statement = match is_good {
debug!(target: "consensus", "Making validity statement about candidate {}: is_good? {:?}", hash, is_good); true => GenericStatement::Valid(candidate_hash),
statements.validity = match is_good { false => GenericStatement::Invalid(candidate_hash),
Some(true) => Some(GenericStatement::Valid(hash)),
Some(false) => Some(GenericStatement::Invalid(hash)),
None => None,
}; };
work.evaluate = false; let extrinsic = Extrinsic;
}
}
if let Async::Ready(Some(extrinsic)) = work.fetch_extrinsic.poll()? {
if work.ensure_available {
let hash = candidate_hash();
debug!(target: "consensus", "Claiming candidate {} available.", hash);
statements.extrinsic = Some(extrinsic);
statements.availability = Some(GenericStatement::Available(hash));
work.ensure_available = false;
}
}
let done = match (work.evaluate, work.ensure_available) {
(false, false) => true,
_ => false,
};
if done {
// commit claimed-available data to disk before returning statements from the future.
if let (&Some(ref block), extrinsic) = (&statements.block_data, &statements.extrinsic) {
self.inner.extrinsic_store.make_available(Data { self.inner.extrinsic_store.make_available(Data {
relay_parent: self.inner.relay_parent, relay_parent: self.inner.relay_parent,
parachain_id: work.candidate_receipt.parachain_index, parachain_id: work.candidate_receipt.parachain_index,
candidate_hash: candidate_hash(), candidate_hash,
block_data: block.clone(), block_data: block.clone(),
extrinsic: extrinsic.clone(), extrinsic: Some(extrinsic.clone()),
})?; })?;
}
Ok(Async::Ready(::std::mem::replace(statements, Default::default()))) Ok(Async::Ready(Validated {
} else { validity: validity_statement,
Ok(Async::NotReady) block_data: block,
} extrinsic,
}))
} }
} }
@@ -334,7 +292,6 @@ impl SharedTable {
table: Table::default(), table: Table::default(),
proposed_digest: None, proposed_digest: None,
checked_validity: HashSet::new(), checked_validity: HashSet::new(),
checked_availability: HashSet::new(),
trackers: Vec::new(), trackers: Vec::new(),
extrinsic_store, extrinsic_store,
})) }))
@@ -364,9 +321,8 @@ impl SharedTable {
&self, &self,
router: &R, router: &R,
statement: table::SignedStatement, statement: table::SignedStatement,
) -> Option<StatementProducer< ) -> Option<ParachainWork<
<R::FetchCandidate as IntoFuture>::Future, <R::FetchCandidate as IntoFuture>::Future,
<R::FetchExtrinsic as IntoFuture>::Future,
>> { >> {
self.inner.lock().import_remote_statement(&*self.context, router, statement) self.inner.lock().import_remote_statement(&*self.context, router, statement)
} }
@@ -381,9 +337,8 @@ impl SharedTable {
where where
R: TableRouter, R: TableRouter,
I: IntoIterator<Item=table::SignedStatement>, I: IntoIterator<Item=table::SignedStatement>,
U: ::std::iter::FromIterator<Option<StatementProducer< U: ::std::iter::FromIterator<Option<ParachainWork<
<R::FetchCandidate as IntoFuture>::Future, <R::FetchCandidate as IntoFuture>::Future,
<R::FetchExtrinsic as IntoFuture>::Future,
>>>, >>>,
{ {
let mut inner = self.inner.lock(); let mut inner = self.inner.lock();
@@ -394,25 +349,12 @@ impl SharedTable {
} }
/// Sign and import a local statement. /// Sign and import a local statement.
///
/// For candidate statements, this may also produce a second signed statement
/// concerning the availability of the candidate data.
pub fn sign_and_import(&self, statement: table::Statement) pub fn sign_and_import(&self, statement: table::Statement)
-> (SignedStatement, Option<SignedStatement>) -> SignedStatement
{ {
let (proposed_digest, availability) = match statement { let proposed_digest = match statement {
GenericStatement::Candidate(ref c) => { GenericStatement::Candidate(ref c) => Some(c.hash()),
let mut availability = None; _ => None,
let hash = c.hash();
// TODO: actually store the data in an availability store of some kind.
if self.context.is_availability_guarantor_of(&self.context.local_id(), &c.parachain_index) {
availability = Some(self.context.sign_statement(GenericStatement::Available(hash)));
}
(Some(hash), availability)
}
_ => (None, None),
}; };
let signed_statement = self.context.sign_statement(statement); let signed_statement = self.context.sign_statement(statement);
@@ -424,12 +366,7 @@ impl SharedTable {
inner.table.import_statement(&*self.context, signed_statement.clone()); inner.table.import_statement(&*self.context, signed_statement.clone());
// ensure the availability statement is imported after the candidate. signed_statement
if let Some(a) = availability.clone() {
inner.table.import_statement(&*self.context, a);
}
(signed_statement, availability)
} }
/// Execute a closure using a specific candidate. /// Execute a closure using a specific candidate.
@@ -454,7 +391,6 @@ impl SharedTable {
table_attestations.into_iter() table_attestations.into_iter()
.map(|attested| AttestedCandidate { .map(|attested| AttestedCandidate {
candidate: attested.candidate, candidate: attested.candidate,
availability_votes: attested.availability_votes,
validity_votes: attested.validity_votes.into_iter().map(|(a, v)| match v { validity_votes: attested.validity_votes.into_iter().map(|(a, v)| match v {
GAttestation::Implicit(s) => (a, ValidityAttestation::Implicit(s)), GAttestation::Implicit(s) => (a, ValidityAttestation::Implicit(s)),
GAttestation::Explicit(s) => (a, ValidityAttestation::Explicit(s)), GAttestation::Explicit(s) => (a, ValidityAttestation::Explicit(s)),
@@ -468,7 +404,7 @@ impl SharedTable {
self.group_info().len() self.group_info().len()
} }
/// Get the number of parachains which have available candidates. /// Get the number of parachains whose candidates may be included.
pub fn includable_count(&self) -> usize { pub fn includable_count(&self) -> usize {
self.inner.lock().table.includable_count() self.inner.lock().table.includable_count()
} }
@@ -501,22 +437,23 @@ impl SharedTable {
mod tests { mod tests {
use super::*; use super::*;
use substrate_keyring::Keyring; use substrate_keyring::Keyring;
use futures::future;
#[derive(Clone)] #[derive(Clone)]
struct DummyRouter; struct DummyRouter;
impl TableRouter for DummyRouter { impl TableRouter for DummyRouter {
type Error = ::std::io::Error; type Error = ::std::io::Error;
type FetchCandidate = ::futures::future::Empty<BlockData,Self::Error>; type FetchCandidate = ::futures::future::FutureResult<BlockData,Self::Error>;
type FetchExtrinsic = ::futures::future::Empty<Extrinsic,Self::Error>; type FetchExtrinsic = ::futures::future::FutureResult<Extrinsic,Self::Error>;
fn local_candidate(&self, _candidate: CandidateReceipt, _block_data: BlockData, _extrinsic: Extrinsic) { fn local_candidate(&self, _candidate: CandidateReceipt, _block_data: BlockData, _extrinsic: Extrinsic) {
} }
fn fetch_block_data(&self, _candidate: &CandidateReceipt) -> Self::FetchCandidate { fn fetch_block_data(&self, _candidate: &CandidateReceipt) -> Self::FetchCandidate {
::futures::future::empty() future::ok(BlockData(vec![1, 2, 3, 4, 5]))
} }
fn fetch_extrinsic_data(&self, _candidate: &CandidateReceipt) -> Self::FetchExtrinsic { fn fetch_extrinsic_data(&self, _candidate: &CandidateReceipt) -> Self::FetchExtrinsic {
::futures::future::empty() future::ok(Extrinsic)
} }
} }
@@ -534,9 +471,7 @@ mod tests {
groups.insert(para_id, GroupInfo { groups.insert(para_id, GroupInfo {
validity_guarantors: [local_id, validity_other].iter().cloned().collect(), validity_guarantors: [local_id, validity_other].iter().cloned().collect(),
availability_guarantors: Default::default(),
needed_validity: 2, needed_validity: 2,
needed_availability: 0,
}); });
let shared_table = SharedTable::new( let shared_table = SharedTable::new(
@@ -566,17 +501,14 @@ mod tests {
sender: validity_other, sender: validity_other,
}; };
let producer = shared_table.import_remote_statement( shared_table.import_remote_statement(
&DummyRouter, &DummyRouter,
signed_statement, signed_statement,
).expect("candidate and local validity group are same"); ).expect("candidate and local validity group are same");
assert!(producer.work.evaluate, "should evaluate validity");
assert!(producer.work.fetch_extrinsic.is_none(), "should not fetch extrinsic");
} }
#[test] #[test]
fn statement_triggers_fetch_and_availability() { fn statement_triggers_fetch_and_validity() {
let mut groups = HashMap::new(); let mut groups = HashMap::new();
let para_id = ParaId::from(1); let para_id = ParaId::from(1);
@@ -588,10 +520,8 @@ mod tests {
let parent_hash = Default::default(); let parent_hash = Default::default();
groups.insert(para_id, GroupInfo { groups.insert(para_id, GroupInfo {
validity_guarantors: [validity_other].iter().cloned().collect(), validity_guarantors: [local_id, validity_other].iter().cloned().collect(),
availability_guarantors: [local_id].iter().cloned().collect(),
needed_validity: 1, needed_validity: 1,
needed_availability: 1,
}); });
let shared_table = SharedTable::new( let shared_table = SharedTable::new(
@@ -621,14 +551,10 @@ mod tests {
sender: validity_other, sender: validity_other,
}; };
let producer = shared_table.import_remote_statement( shared_table.import_remote_statement(
&DummyRouter, &DummyRouter,
signed_statement, signed_statement,
).expect("should produce work"); ).expect("should produce work");
assert!(producer.work.fetch_extrinsic.is_some(), "should fetch extrinsic when guaranteeing availability");
assert!(!producer.work.evaluate, "should not evaluate validity");
assert!(producer.work.ensure_available);
} }
#[test] #[test]
@@ -651,28 +577,22 @@ mod tests {
let hash = candidate.hash(); let hash = candidate.hash();
let block_data_res: ::std::io::Result<_> = Ok(block_data.clone()); let producer: ParachainWork<future::FutureResult<_, ::std::io::Error>> = ParachainWork {
let producer: StatementProducer<_, future::Empty<_, _>> = StatementProducer {
produced_statements: Default::default(),
work: Work { work: Work {
candidate_receipt: candidate, candidate_receipt: candidate,
fetch_block_data: block_data_res.into_future().fuse(), fetch_block_data: future::ok(block_data.clone()),
fetch_extrinsic: None,
evaluate: true,
ensure_available: false,
}, },
relay_parent, relay_parent,
extrinsic_store: store.clone(), extrinsic_store: store.clone(),
}; };
let produced = producer.prime(|_| Some(true)).wait().unwrap(); let produced = producer.prime_with(|_, _| true).wait().unwrap();
assert_eq!(produced.block_data.as_ref(), Some(&block_data)); assert_eq!(produced.block_data, block_data);
assert!(produced.validity.is_some()); assert_eq!(produced.validity, GenericStatement::Valid(hash));
assert!(produced.availability.is_none());
assert_eq!(store.block_data(relay_parent, hash).unwrap(), block_data); assert_eq!(store.block_data(relay_parent, hash).unwrap(), block_data);
assert!(store.extrinsic(relay_parent, hash).is_none()); assert!(store.extrinsic(relay_parent, hash).is_some());
} }
#[test] #[test]
@@ -695,26 +615,18 @@ mod tests {
let hash = candidate.hash(); let hash = candidate.hash();
let block_data_res: ::std::io::Result<_> = Ok(block_data.clone()); let producer = ParachainWork {
let extrinsic_res: ::std::io::Result<_> = Ok(Extrinsic);
let producer = StatementProducer {
produced_statements: Default::default(),
work: Work { work: Work {
candidate_receipt: candidate, candidate_receipt: candidate,
fetch_block_data: block_data_res.into_future().fuse(), fetch_block_data: future::ok::<_, ::std::io::Error>(block_data.clone()),
fetch_extrinsic: Some(extrinsic_res.into_future().fuse()),
evaluate: false,
ensure_available: true,
}, },
relay_parent, relay_parent,
extrinsic_store: store.clone(), extrinsic_store: store.clone(),
}; };
let produced = producer.prime(|_| Some(true)).wait().unwrap(); let produced = producer.prime_with(|_, _| true).wait().unwrap();
assert_eq!(produced.block_data.as_ref(), Some(&block_data)); assert_eq!(produced.block_data, block_data);
assert!(produced.validity.is_none());
assert!(produced.availability.is_some());
assert_eq!(store.block_data(relay_parent, hash).unwrap(), block_data); assert_eq!(store.block_data(relay_parent, hash).unwrap(), block_data);
assert!(store.extrinsic(relay_parent, hash).is_some()); assert!(store.extrinsic(relay_parent, hash).is_some());
-5
View File
@@ -237,11 +237,6 @@ impl Knowledge {
entry.knows_block_data.push(from); entry.knows_block_data.push(from);
entry.knows_extrinsic.push(from); entry.knows_extrinsic.push(from);
} }
GenericStatement::Available(ref hash) => {
let mut entry = self.candidates.entry(*hash).or_insert_with(Default::default);
entry.knows_block_data.push(from);
entry.knows_extrinsic.push(from);
}
GenericStatement::Valid(ref hash) | GenericStatement::Invalid(ref hash) => self.candidates.entry(*hash) GenericStatement::Valid(ref hash) | GenericStatement::Invalid(ref hash) => self.candidates.entry(*hash)
.or_insert_with(Default::default) .or_insert_with(Default::default)
.knows_block_data .knows_block_data
+9 -44
View File
@@ -23,8 +23,8 @@
//! and dispatch evaluation work as necessary when new statements come in. //! and dispatch evaluation work as necessary when new statements come in.
use sr_primitives::traits::{ProvideRuntimeApi, BlakeTwo256, Hash as HashT}; use sr_primitives::traits::{ProvideRuntimeApi, BlakeTwo256, Hash as HashT};
use polkadot_consensus::{SharedTable, TableRouter, SignedStatement, GenericStatement, StatementProducer}; use polkadot_consensus::{SharedTable, TableRouter, SignedStatement, GenericStatement, ParachainWork};
use polkadot_primitives::{Block, Hash, BlockId, SessionKey}; use polkadot_primitives::{Block, Hash, SessionKey};
use polkadot_primitives::parachain::{BlockData, Extrinsic, CandidateReceipt, ParachainHost}; use polkadot_primitives::parachain::{BlockData, Extrinsic, CandidateReceipt, ParachainHost};
use codec::Encode; use codec::Encode;
@@ -115,7 +115,6 @@ impl<P: ProvideRuntimeApi + Send + Sync + 'static> Router<P>
GenericStatement::Candidate(ref c) => Some(c.hash()), GenericStatement::Candidate(ref c) => Some(c.hash()),
GenericStatement::Valid(ref hash) GenericStatement::Valid(ref hash)
| GenericStatement::Invalid(ref hash) | GenericStatement::Invalid(ref hash)
| GenericStatement::Available(ref hash)
=> self.table.with_candidate(hash, |c| c.map(|_| *hash)), => self.table.with_candidate(hash, |c| c.map(|_| *hash)),
}; };
match candidate_data { match candidate_data {
@@ -152,61 +151,33 @@ impl<P: ProvideRuntimeApi + Send + Sync + 'static> Router<P>
} }
} }
fn create_work<D, E>(&self, candidate_hash: Hash, producer: StatementProducer<D, E>) fn create_work<D>(&self, candidate_hash: Hash, producer: ParachainWork<D>)
-> impl Future<Item=(),Error=()> -> impl Future<Item=(),Error=()>
where where
D: Future<Item=BlockData,Error=io::Error> + Send + 'static, D: Future<Item=BlockData,Error=io::Error> + Send + 'static,
E: Future<Item=Extrinsic,Error=io::Error> + Send + 'static,
{ {
let parent_hash = self.parent_hash.clone();
let api = self.api.clone();
let validate = move |collation| -> Option<bool> {
let id = BlockId::hash(parent_hash);
match ::polkadot_consensus::validate_collation(&*api, &id, &collation) {
Ok(()) => Some(true),
Err(e) => {
debug!(target: "p_net", "Encountered bad collation: {}", e);
Some(false)
}
}
};
let table = self.table.clone(); let table = self.table.clone();
let network = self.network.clone(); let network = self.network.clone();
let knowledge = self.knowledge.clone(); let knowledge = self.knowledge.clone();
let attestation_topic = self.attestation_topic.clone(); let attestation_topic = self.attestation_topic.clone();
producer.prime(validate) producer.prime(self.api.clone())
.map(move |produced| { .map(move |produced| {
// store the data before broadcasting statements, so other peers can fetch. // store the data before broadcasting statements, so other peers can fetch.
knowledge.lock().note_candidate( knowledge.lock().note_candidate(
candidate_hash, candidate_hash,
produced.block_data, Some(produced.block_data),
produced.extrinsic, Some(produced.extrinsic),
); );
if produced.validity.is_none() && produced.availability.is_none() {
return
}
let mut gossip = network.consensus_gossip().write(); let mut gossip = network.consensus_gossip().write();
// propagate the statements // propagate the statement.
// consider something more targeted than gossip in the future. // consider something more targeted than gossip in the future.
if let Some(validity) = produced.validity { let signed = table.sign_and_import(produced.validity);
let signed = table.sign_and_import(validity.clone()).0;
network.with_spec(|_, ctx| network.with_spec(|_, ctx|
gossip.multicast(ctx, attestation_topic, signed.encode(), false) gossip.multicast(ctx, attestation_topic, signed.encode(), false)
); );
}
if let Some(availability) = produced.availability {
let signed = table.sign_and_import(availability).0;
network.with_spec(|_, ctx|
gossip.multicast(ctx, attestation_topic, signed.encode(), false)
);
}
}) })
.map_err(|e| debug!(target: "p_net", "Failed to produce statements: {:?}", e)) .map_err(|e| debug!(target: "p_net", "Failed to produce statements: {:?}", e))
} }
@@ -222,15 +193,12 @@ impl<P: ProvideRuntimeApi + Send> TableRouter for Router<P>
fn local_candidate(&self, receipt: CandidateReceipt, block_data: BlockData, extrinsic: Extrinsic) { fn local_candidate(&self, receipt: CandidateReceipt, block_data: BlockData, extrinsic: Extrinsic) {
// give to network to make available. // give to network to make available.
let hash = receipt.hash(); let hash = receipt.hash();
let (candidate, availability) = self.table.sign_and_import(GenericStatement::Candidate(receipt)); let candidate = self.table.sign_and_import(GenericStatement::Candidate(receipt));
self.knowledge.lock().note_candidate(hash, Some(block_data), Some(extrinsic)); self.knowledge.lock().note_candidate(hash, Some(block_data), Some(extrinsic));
let mut gossip = self.network.consensus_gossip().write(); let mut gossip = self.network.consensus_gossip().write();
self.network.with_spec(|_spec, ctx| { self.network.with_spec(|_spec, ctx| {
gossip.multicast(ctx, self.attestation_topic, candidate.encode(), false); gossip.multicast(ctx, self.attestation_topic, candidate.encode(), false);
if let Some(availability) = availability {
gossip.multicast(ctx, self.attestation_topic, availability.encode(), false);
}
}); });
} }
@@ -274,7 +242,6 @@ impl Future for BlockDataReceiver {
enum StatementTrace { enum StatementTrace {
Valid(SessionKey, Hash), Valid(SessionKey, Hash),
Invalid(SessionKey, Hash), Invalid(SessionKey, Hash),
Available(SessionKey, Hash),
} }
// helper for deferring statements whose associated candidate is unknown. // helper for deferring statements whose associated candidate is unknown.
@@ -296,7 +263,6 @@ impl DeferredStatements {
GenericStatement::Candidate(_) => return, GenericStatement::Candidate(_) => return,
GenericStatement::Valid(hash) => (hash, StatementTrace::Valid(statement.sender, hash)), GenericStatement::Valid(hash) => (hash, StatementTrace::Valid(statement.sender, hash)),
GenericStatement::Invalid(hash) => (hash, StatementTrace::Invalid(statement.sender, hash)), GenericStatement::Invalid(hash) => (hash, StatementTrace::Invalid(statement.sender, hash)),
GenericStatement::Available(hash) => (hash, StatementTrace::Available(statement.sender, hash)),
}; };
if self.known_traces.insert(trace) { if self.known_traces.insert(trace) {
@@ -314,7 +280,6 @@ impl DeferredStatements {
GenericStatement::Candidate(_) => continue, GenericStatement::Candidate(_) => continue,
GenericStatement::Valid(hash) => StatementTrace::Valid(statement.sender, hash), GenericStatement::Valid(hash) => StatementTrace::Valid(statement.sender, hash),
GenericStatement::Invalid(hash) => StatementTrace::Invalid(statement.sender, hash), GenericStatement::Invalid(hash) => StatementTrace::Invalid(statement.sender, hash),
GenericStatement::Available(hash) => StatementTrace::Available(statement.sender, hash),
}; };
self.known_traces.remove(&trace); self.known_traces.remove(&trace);
+1 -1
View File
@@ -86,7 +86,7 @@ pub struct ValidationParams {
#[cfg_attr(feature = "std", derive(Debug))] #[cfg_attr(feature = "std", derive(Debug))]
pub struct ValidationResult { pub struct ValidationResult {
/// New head data that should be included in the relay chain state. /// New head data that should be included in the relay chain state.
pub head_data: Vec<u8> pub head_data: Vec<u8>,
} }
/// Load the validation params from memory when implementing a Rust parachain. /// Load the validation params from memory when implementing a Rust parachain.
-8
View File
@@ -64,9 +64,6 @@ pub enum Chain {
pub struct DutyRoster { pub struct DutyRoster {
/// Lookup from validator index to chain on which that validator has a duty to validate. /// Lookup from validator index to chain on which that validator has a duty to validate.
pub validator_duty: Vec<Chain>, pub validator_duty: Vec<Chain>,
/// Lookup from validator index to chain on which that validator has a duty to guarantee
/// availability.
pub guarantor_duty: Vec<Chain>,
} }
/// Extrinsic data for a parachain. /// Extrinsic data for a parachain.
@@ -206,9 +203,6 @@ pub enum Statement {
/// State a candidate is invalid. /// State a candidate is invalid.
#[codec(index = "3")] #[codec(index = "3")]
Invalid(Hash), Invalid(Hash),
/// State a candidate's associated data is unavailable.
#[codec(index = "4")]
Available(Hash),
} }
/// An either implicit or explicit attestation to the validity of a parachain /// An either implicit or explicit attestation to the validity of a parachain
@@ -234,8 +228,6 @@ pub struct AttestedCandidate {
pub candidate: CandidateReceipt, pub candidate: CandidateReceipt,
/// Validity attestations. /// Validity attestations.
pub validity_votes: Vec<(SessionKey, ValidityAttestation)>, pub validity_votes: Vec<(SessionKey, ValidityAttestation)>,
/// Availability attestations.
pub availability_votes: Vec<(SessionKey, CandidateSignature)>,
} }
impl AttestedCandidate { impl AttestedCandidate {
+7 -75
View File
@@ -196,8 +196,6 @@ impl<T: Trait> Module<T> {
_ => Chain::Relay, _ => Chain::Relay,
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
let mut roles_gua = roles_val.clone();
let mut random_seed = system::Module::<T>::random_seed().as_ref().to_vec(); let mut random_seed = system::Module::<T>::random_seed().as_ref().to_vec();
random_seed.extend(b"validator_role_pairs"); random_seed.extend(b"validator_role_pairs");
let mut seed = BlakeTwo256::hash(&random_seed); let mut seed = BlakeTwo256::hash(&random_seed);
@@ -212,7 +210,6 @@ impl<T: Trait> Module<T> {
// 4 * 2 32-bit ints per 256-bit seed. // 4 * 2 32-bit ints per 256-bit seed.
let val_index = u32::decode(&mut &seed[offset..offset + 4]).expect("using 4 bytes for a 32-bit quantity") as usize % remaining; let val_index = u32::decode(&mut &seed[offset..offset + 4]).expect("using 4 bytes for a 32-bit quantity") as usize % remaining;
let gua_index = u32::decode(&mut &seed[offset + 4..offset + 8]).expect("using 4 bytes for a 32-bit quantity") as usize % remaining;
if offset == 24 { if offset == 24 {
// into the last 8 bytes - rehash to gather new entropy // into the last 8 bytes - rehash to gather new entropy
@@ -221,12 +218,10 @@ impl<T: Trait> Module<T> {
// exchange last item with randomly chosen first. // exchange last item with randomly chosen first.
roles_val.swap(remaining - 1, val_index); roles_val.swap(remaining - 1, val_index);
roles_gua.swap(remaining - 1, gua_index);
} }
DutyRoster { DutyRoster {
validator_duty: roles_val, validator_duty: roles_val,
guarantor_duty: roles_gua,
} }
} }
@@ -302,38 +297,27 @@ impl<T: Trait> Module<T> {
}; };
let sorted_validators = make_sorted_duties(&duty_roster.validator_duty); let sorted_validators = make_sorted_duties(&duty_roster.validator_duty);
let sorted_guarantors = make_sorted_duties(&duty_roster.guarantor_duty);
let parent_hash = super::System::parent_hash(); let parent_hash = super::System::parent_hash();
let localized_payload = |statement: Statement| localized_payload(statement, parent_hash); let localized_payload = |statement: Statement| localized_payload(statement, parent_hash);
let mut validator_groups = GroupedDutyIter::new(&sorted_validators[..]); let mut validator_groups = GroupedDutyIter::new(&sorted_validators[..]);
let mut guarantor_groups = GroupedDutyIter::new(&sorted_guarantors[..]);
for candidate in attested_candidates { for candidate in attested_candidates {
let validator_group = validator_groups.group_for(candidate.parachain_index()) let validator_group = validator_groups.group_for(candidate.parachain_index())
.ok_or("no validator group for parachain")?; .ok_or("no validator group for parachain")?;
let availability_group = guarantor_groups.group_for(candidate.parachain_index())
.ok_or("no availability group for parachain")?;
ensure!( ensure!(
candidate.validity_votes.len() >= majority_of(validator_group.len()), candidate.validity_votes.len() >= majority_of(validator_group.len()),
"Not enough validity attestations" "Not enough validity attestations"
); );
ensure!(
candidate.availability_votes.len() >= majority_of(availability_group.len()),
"Not enough availability attestations"
);
let mut candidate_hash = None; let mut candidate_hash = None;
let mut encoded_implicit = None; let mut encoded_implicit = None;
let mut encoded_explicit = None; let mut encoded_explicit = None;
// track which voters have voted already. the first `authorities.len()` // track which voters have voted already, 1 bit per authority.
// bits is for validity, the next are for availability. let mut track_voters = bitvec![0; authorities.len()];
let mut track_voters = bitvec![0; authorities.len() * 2];
for (auth_id, validity_attestation) in &candidate.validity_votes { for (auth_id, validity_attestation) in &candidate.validity_votes {
// protect against double-votes. // protect against double-votes.
match validator_group.iter().find(|&(idx, _)| &authorities[*idx] == auth_id) { match validator_group.iter().find(|&(idx, _)| &authorities[*idx] == auth_id) {
@@ -372,32 +356,6 @@ impl<T: Trait> Module<T> {
"Candidate validity attestation signature is bad." "Candidate validity attestation signature is bad."
); );
} }
let mut encoded_available = None;
for (auth_id, sig) in &candidate.availability_votes {
match availability_group.iter().find(|&(idx, _)| &authorities[*idx] == auth_id) {
None => return Err("Attesting validator not on this chain's availability duty."),
Some(&(idx, _)) => {
if track_voters.get(authorities.len() + idx) {
return Err("Voter already attested availability once")
}
track_voters.set(authorities.len() + idx, true)
}
}
let hash = candidate_hash
.get_or_insert_with(|| candidate.candidate.hash())
.clone();
let payload = encoded_available.get_or_insert_with(|| localized_payload(
Statement::Available(hash),
));
ensure!(
sig.verify(&payload[..], &auth_id.0.into()),
"Candidate availability attestation signature is bad."
)
}
} }
Ok(()) Ok(())
@@ -544,39 +502,28 @@ mod tests {
}; };
let validation_entries = duty_roster.validator_duty.iter() let validation_entries = duty_roster.validator_duty.iter()
.enumerate() .enumerate();
.map(|(i, d)| (i, d, true));
let availability_entries = duty_roster.guarantor_duty.iter() for (idx, &duty) in validation_entries {
.enumerate()
.map(|(i, d)| (i, d, false));
for (idx, &duty, is_validation) in validation_entries.chain(availability_entries) {
if duty != Chain::Parachain(candidate.parachain_index()) { continue } if duty != Chain::Parachain(candidate.parachain_index()) { continue }
if is_validation { vote_implicit = !vote_implicit }; vote_implicit = !vote_implicit;
let key = extract_key(authorities[idx]); let key = extract_key(authorities[idx]);
let statement = if is_validation && vote_implicit { let statement = if vote_implicit {
Statement::Candidate(candidate.candidate.clone()) Statement::Candidate(candidate.candidate.clone())
} else if is_validation {
Statement::Valid(candidate_hash.clone())
} else { } else {
Statement::Available(candidate_hash.clone()) Statement::Valid(candidate_hash.clone())
}; };
let payload = localized_payload(statement, parent_hash); let payload = localized_payload(statement, parent_hash);
let signature = key.sign(&payload[..]).into(); let signature = key.sign(&payload[..]).into();
if is_validation {
candidate.validity_votes.push((authorities[idx], if vote_implicit { candidate.validity_votes.push((authorities[idx], if vote_implicit {
ValidityAttestation::Implicit(signature) ValidityAttestation::Implicit(signature)
} else { } else {
ValidityAttestation::Explicit(signature) ValidityAttestation::Explicit(signature)
})); }));
} else {
candidate.availability_votes.push((authorities[idx], signature));
}
} }
} }
@@ -629,13 +576,10 @@ mod tests {
with_externalities(&mut new_test_ext(parachains), || { with_externalities(&mut new_test_ext(parachains), || {
let check_roster = |duty_roster: &DutyRoster| { let check_roster = |duty_roster: &DutyRoster| {
assert_eq!(duty_roster.validator_duty.len(), 8); assert_eq!(duty_roster.validator_duty.len(), 8);
assert_eq!(duty_roster.guarantor_duty.len(), 8);
for i in (0..2).map(ParaId::from) { for i in (0..2).map(ParaId::from) {
assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Parachain(i)).count(), 3); assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Parachain(i)).count(), 3);
assert_eq!(duty_roster.guarantor_duty.iter().filter(|&&j| j == Chain::Parachain(i)).count(), 3);
} }
assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Relay).count(), 2); assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Relay).count(), 2);
assert_eq!(duty_roster.guarantor_duty.iter().filter(|&&j| j == Chain::Relay).count(), 2);
}; };
system::Module::<Test>::set_random_seed([0u8; 32].into()); system::Module::<Test>::set_random_seed([0u8; 32].into());
@@ -667,7 +611,6 @@ mod tests {
system::Module::<Test>::set_random_seed([0u8; 32].into()); system::Module::<Test>::set_random_seed([0u8; 32].into());
let candidate = AttestedCandidate { let candidate = AttestedCandidate {
validity_votes: vec![], validity_votes: vec![],
availability_votes: vec![],
candidate: CandidateReceipt { candidate: CandidateReceipt {
parachain_index: 0.into(), parachain_index: 0.into(),
collator: Default::default(), collator: Default::default(),
@@ -695,7 +638,6 @@ mod tests {
system::Module::<Test>::set_random_seed([0u8; 32].into()); system::Module::<Test>::set_random_seed([0u8; 32].into());
let mut candidate_a = AttestedCandidate { let mut candidate_a = AttestedCandidate {
validity_votes: vec![], validity_votes: vec![],
availability_votes: vec![],
candidate: CandidateReceipt { candidate: CandidateReceipt {
parachain_index: 0.into(), parachain_index: 0.into(),
collator: Default::default(), collator: Default::default(),
@@ -710,7 +652,6 @@ mod tests {
let mut candidate_b = AttestedCandidate { let mut candidate_b = AttestedCandidate {
validity_votes: vec![], validity_votes: vec![],
availability_votes: vec![],
candidate: CandidateReceipt { candidate: CandidateReceipt {
parachain_index: 1.into(), parachain_index: 1.into(),
collator: Default::default(), collator: Default::default(),
@@ -749,7 +690,6 @@ mod tests {
system::Module::<Test>::set_random_seed([0u8; 32].into()); system::Module::<Test>::set_random_seed([0u8; 32].into());
let mut candidate = AttestedCandidate { let mut candidate = AttestedCandidate {
validity_votes: vec![], validity_votes: vec![],
availability_votes: vec![],
candidate: CandidateReceipt { candidate: CandidateReceipt {
parachain_index: 0.into(), parachain_index: 0.into(),
collator: Default::default(), collator: Default::default(),
@@ -771,14 +711,6 @@ mod tests {
Call::set_heads(vec![double_validity]), Call::set_heads(vec![double_validity]),
Origin::INHERENT, Origin::INHERENT,
).is_err()); ).is_err());
let mut double_availability = candidate.clone();
double_availability.availability_votes.push(candidate.availability_votes[0].clone());
assert!(Parachains::dispatch(
Call::set_heads(vec![double_availability]),
Origin::INHERENT,
).is_err());
}); });
} }
} }
+48 -228
View File
@@ -20,12 +20,9 @@
//! //!
//! These messages are used to create a proposal submitted to a BFT consensus process. //! These messages are used to create a proposal submitted to a BFT consensus process.
//! //!
//! Proposals are formed of sets of candidates which have the requisite number of //! Each parachain is associated with a committee of authorities, who issue statements
//! validity and availability votes. //! indicating whether the candidate is valid or invalid. Once a threshold of the committee
//! //! has signed validity statements, the candidate may be marked includable.
//! Each parachain is associated with two sets of authorities: those which can
//! propose and attest to validity of candidates, and those who can only attest
//! to availability.
use std::collections::hash_map::{HashMap, Entry}; use std::collections::hash_map::{HashMap, Entry};
use std::hash::Hash; use std::hash::Hash;
@@ -54,17 +51,8 @@ pub trait Context {
/// Members are meant to submit candidates and vote on validity. /// Members are meant to submit candidates and vote on validity.
fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool; fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool;
/// Whether a authority is an availability guarantor of a group. // requisite number of votes for validity from a group.
/// Guarantors are meant to vote on availability for candidates submitted fn requisite_votes(&self, group: &Self::GroupId) -> usize;
/// in a group.
fn is_availability_guarantor_of(
&self,
authority: &Self::AuthorityId,
group: &Self::GroupId,
) -> bool;
// requisite number of votes for validity and availability respectively from a group.
fn requisite_votes(&self, group: &Self::GroupId) -> (usize, usize);
} }
/// Statements circulated among peers. /// Statements circulated among peers.
@@ -84,10 +72,6 @@ pub enum Statement<C, D> {
/// is invalid. /// is invalid.
#[codec(index = "3")] #[codec(index = "3")]
Invalid(D), Invalid(D),
/// Broadcast by a authority to attest that the auxiliary data for a candidate
/// with given digest is available.
#[codec(index = "4")]
Available(D),
} }
/// A signed statement. /// A signed statement.
@@ -124,8 +108,6 @@ pub enum DoubleSign<C, D, S> {
Validity(D, S, S), Validity(D, S, S),
/// On invalidity. /// On invalidity.
Invalidity(D, S, S), Invalidity(D, S, S),
/// On availability.
Availability(D, S, S),
} }
/// Misbehavior: declaring multiple candidates. /// Misbehavior: declaring multiple candidates.
@@ -181,8 +163,6 @@ pub struct Summary<D, G> {
pub group_id: G, pub group_id: G,
/// How many validity votes are currently witnessed. /// How many validity votes are currently witnessed.
pub validity_votes: usize, pub validity_votes: usize,
/// How many availability votes are currently witnessed.
pub availability_votes: usize,
/// Whether this has been signalled bad by at least one participant. /// Whether this has been signalled bad by at least one participant.
pub signalled_bad: bool, pub signalled_bad: bool,
} }
@@ -207,8 +187,6 @@ pub struct AttestedCandidate<Group, Candidate, AuthorityId, Signature> {
pub candidate: Candidate, pub candidate: Candidate,
/// Validity attestations. /// Validity attestations.
pub validity_votes: Vec<(AuthorityId, ValidityAttestation<Signature>)>, pub validity_votes: Vec<(AuthorityId, ValidityAttestation<Signature>)>,
/// Availability attestations.
pub availability_votes: Vec<(AuthorityId, Signature)>
} }
/// Stores votes and data about a candidate. /// Stores votes and data about a candidate.
@@ -216,7 +194,6 @@ pub struct CandidateData<C: Context> {
group_id: C::GroupId, group_id: C::GroupId,
candidate: C::Candidate, candidate: C::Candidate,
validity_votes: HashMap<C::AuthorityId, ValidityVote<C::Signature>>, validity_votes: HashMap<C::AuthorityId, ValidityVote<C::Signature>>,
availability_votes: HashMap<C::AuthorityId, C::Signature>,
indicated_bad_by: Vec<C::AuthorityId>, indicated_bad_by: Vec<C::AuthorityId>,
} }
@@ -228,12 +205,12 @@ impl<C: Context> CandidateData<C> {
/// Yield a full attestation for a candidate. /// Yield a full attestation for a candidate.
/// If the candidate can be included, it will return `Some`. /// If the candidate can be included, it will return `Some`.
pub fn attested(&self, validity_threshold: usize, availability_threshold: usize) pub fn attested(&self, validity_threshold: usize)
-> Option<AttestedCandidate< -> Option<AttestedCandidate<
C::GroupId, C::Candidate, C::AuthorityId, C::Signature, C::GroupId, C::Candidate, C::AuthorityId, C::Signature,
>> >>
{ {
if self.can_be_included(validity_threshold, availability_threshold) { if self.can_be_included(validity_threshold) {
let validity_votes: Vec<_> = self.validity_votes.iter() let validity_votes: Vec<_> = self.validity_votes.iter()
.filter_map(|(a, v)| match *v { .filter_map(|(a, v)| match *v {
ValidityVote::Invalid(_) => None, ValidityVote::Invalid(_) => None,
@@ -252,21 +229,10 @@ impl<C: Context> CandidateData<C> {
"candidate is includable; therefore there are enough validity votes; qed", "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 { Some(AttestedCandidate {
group_id: self.group_id.clone(), group_id: self.group_id.clone(),
candidate: self.candidate.clone(), candidate: self.candidate.clone(),
validity_votes, validity_votes,
availability_votes,
}) })
} else { } else {
None None
@@ -274,12 +240,11 @@ impl<C: Context> CandidateData<C> {
} }
// Candidate data can be included in a proposal // Candidate data can be included in a proposal
// if it has enough validity and availability votes // if it has enough validity votes
// and no authorities have called it bad. // and no authorities have called it bad.
fn can_be_included(&self, validity_threshold: usize, availability_threshold: usize) -> bool { fn can_be_included(&self, validity_threshold: usize) -> bool {
self.indicated_bad_by.is_empty() self.indicated_bad_by.is_empty()
&& self.validity_votes.len() >= validity_threshold && self.validity_votes.len() >= validity_threshold
&& self.availability_votes.len() >= availability_threshold
} }
fn summary(&self, digest: C::Digest) -> Summary<C::Digest, C::GroupId> { fn summary(&self, digest: C::Digest) -> Summary<C::Digest, C::GroupId> {
@@ -287,7 +252,6 @@ impl<C: Context> CandidateData<C> {
candidate: digest, candidate: digest,
group_id: self.group_id.clone(), group_id: self.group_id.clone(),
validity_votes: self.validity_votes.len() - self.indicated_bad_by.len(), validity_votes: self.validity_votes.len() - self.indicated_bad_by.len(),
availability_votes: self.availability_votes.len(),
signalled_bad: self.indicated_bad(), signalled_bad: self.indicated_bad(),
} }
} }
@@ -352,12 +316,12 @@ impl<C: Context> Table<C> {
continue continue
} }
let (validity_t, availability_t) = context.requisite_votes(group_id); let threshold = context.requisite_votes(group_id);
if !candidate_data.can_be_included(validity_t, availability_t) { continue } if !candidate_data.can_be_included(threshold) { continue }
match best_candidates.entry(group_id.clone()) { match best_candidates.entry(group_id.clone()) {
BTreeEntry::Vacant(vacant) => { BTreeEntry::Vacant(vacant) => {
vacant.insert((candidate_data, validity_t, availability_t)); vacant.insert((candidate_data, threshold));
}, },
BTreeEntry::Occupied(mut occ) => { BTreeEntry::Occupied(mut occ) => {
let candidate_ref = occ.get_mut(); let candidate_ref = occ.get_mut();
@@ -369,8 +333,8 @@ impl<C: Context> Table<C> {
} }
best_candidates.values() best_candidates.values()
.map(|&(candidate_data, validity_t, availability_t)| .map(|&(candidate_data, threshold)|
candidate_data.attested(validity_t, availability_t) candidate_data.attested(threshold)
.expect("candidate has been checked includable; \ .expect("candidate has been checked includable; \
therefore an attestation can be constructed; qed") therefore an attestation can be constructed; qed")
) )
@@ -380,8 +344,8 @@ impl<C: Context> Table<C> {
/// Whether a candidate can be included. /// Whether a candidate can be included.
pub fn candidate_includable(&self, digest: &C::Digest, context: &C) -> bool { pub fn candidate_includable(&self, digest: &C::Digest, context: &C) -> bool {
self.candidate_votes.get(digest).map_or(false, |data| { self.candidate_votes.get(digest).map_or(false, |data| {
let (v_threshold, a_threshold) = context.requisite_votes(&data.group_id); let v_threshold = context.requisite_votes(&data.group_id);
data.can_be_included(v_threshold, a_threshold) data.can_be_included(v_threshold)
}) })
} }
@@ -415,12 +379,6 @@ impl<C: Context> Table<C> {
digest, digest,
ValidityVote::Invalid(signature), ValidityVote::Invalid(signature),
), ),
Statement::Available(digest) => self.availability_vote(
context,
signer.clone(),
digest,
signature,
),
}; };
match res { match res {
@@ -517,7 +475,6 @@ impl<C: Context> Table<C> {
group_id: group, group_id: group,
candidate: candidate, candidate: candidate,
validity_votes: HashMap::new(), validity_votes: HashMap::new(),
availability_votes: HashMap::new(),
indicated_bad_by: Vec::new(), indicated_bad_by: Vec::new(),
}); });
} }
@@ -542,8 +499,8 @@ impl<C: Context> Table<C> {
Some(votes) => votes, Some(votes) => votes,
}; };
let (v_threshold, a_threshold) = context.requisite_votes(&votes.group_id); let v_threshold = context.requisite_votes(&votes.group_id);
let was_includable = votes.can_be_included(v_threshold, a_threshold); let was_includable = votes.can_be_included(v_threshold);
// check that this authority actually can vote in this group. // check that this authority actually can vote in this group.
if !context.is_member_of(&from, &votes.group_id) { if !context.is_member_of(&from, &votes.group_id) {
@@ -615,46 +572,7 @@ impl<C: Context> Table<C> {
} }
} }
let is_includable = votes.can_be_included(v_threshold, a_threshold); let is_includable = votes.can_be_included(v_threshold);
update_includable_count(&mut self.includable_count, &votes.group_id, was_includable, is_includable);
Ok(Some(votes.summary(digest)))
}
fn availability_vote(
&mut self,
context: &C,
from: C::AuthorityId,
digest: C::Digest,
signature: C::Signature,
) -> ImportResult<C> {
let votes = match self.candidate_votes.get_mut(&digest) {
None => return Ok(None),
Some(votes) => votes,
};
let (v_threshold, a_threshold) = context.requisite_votes(&votes.group_id);
let was_includable = votes.can_be_included(v_threshold, a_threshold);
// check that this authority actually can vote in this group.
if !context.is_availability_guarantor_of(&from, &votes.group_id) {
return Err(Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
statement: SignedStatement {
signature: signature,
statement: Statement::Available(digest),
sender: from,
}
}));
}
match votes.availability_votes.entry(from) {
Entry::Occupied(ref occ) if occ.get() != &signature => return Err(
Misbehavior::DoubleSign(DoubleSign::Availability(digest, signature, occ.get().clone()))
),
entry => { let _ = entry.or_insert(signature); },
}
let is_includable = votes.can_be_included(v_threshold, a_threshold);
update_includable_count(&mut self.includable_count, &votes.group_id, was_includable, is_includable); update_includable_count(&mut self.includable_count, &votes.group_id, was_includable, is_includable);
Ok(Some(votes.summary(digest))) Ok(Some(votes.summary(digest)))
@@ -703,8 +621,8 @@ mod tests {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
struct TestContext { struct TestContext {
// v -> (validity, availability) // v -> parachain group
authorities: HashMap<AuthorityId, (GroupId, GroupId)> authorities: HashMap<AuthorityId, GroupId>
} }
impl Context for TestContext { impl Context for TestContext {
@@ -727,27 +645,17 @@ mod tests {
authority: &AuthorityId, authority: &AuthorityId,
group: &GroupId group: &GroupId
) -> bool { ) -> bool {
self.authorities.get(authority).map(|v| &v.0 == group).unwrap_or(false) self.authorities.get(authority).map(|v| v == group).unwrap_or(false)
} }
fn is_availability_guarantor_of( fn requisite_votes(&self, id: &GroupId) -> usize {
&self,
authority: &AuthorityId,
group: &GroupId
) -> bool {
self.authorities.get(authority).map(|v| &v.1 == group).unwrap_or(false)
}
fn requisite_votes(&self, id: &GroupId) -> (usize, usize) {
let mut total_validity = 0; let mut total_validity = 0;
let mut total_availability = 0;
for &(ref validity, ref availability) in self.authorities.values() { for validity in self.authorities.values() {
if validity == id { total_validity += 1 } if validity == id { total_validity += 1 }
if availability == id { total_availability += 1 }
} }
(total_validity / 2 + 1, total_availability / 2 + 1) total_validity / 2 + 1
} }
} }
@@ -756,7 +664,7 @@ mod tests {
let context = TestContext { let context = TestContext {
authorities: { authorities: {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); map.insert(AuthorityId(1), GroupId(2));
map map
} }
}; };
@@ -792,7 +700,7 @@ mod tests {
let context = TestContext { let context = TestContext {
authorities: { authorities: {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(AuthorityId(1), (GroupId(3), GroupId(455))); map.insert(AuthorityId(1), GroupId(3));
map map
} }
}; };
@@ -823,8 +731,8 @@ mod tests {
let context = TestContext { let context = TestContext {
authorities: { authorities: {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); map.insert(AuthorityId(1), GroupId(2));
map.insert(AuthorityId(2), (GroupId(3), GroupId(222))); map.insert(AuthorityId(2), GroupId(3));
map map
} }
}; };
@@ -838,37 +746,10 @@ mod tests {
}; };
let candidate_a_digest = Digest(100); let candidate_a_digest = Digest(100);
let candidate_b = SignedStatement {
statement: Statement::Candidate(Candidate(3, 987)),
signature: Signature(2),
sender: AuthorityId(2),
};
let candidate_b_digest = Digest(987);
table.import_statement(&context, candidate_a); table.import_statement(&context, candidate_a);
table.import_statement(&context, candidate_b);
assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2)));
// authority 1 votes for availability on 2's candidate.
let bad_availability_vote = SignedStatement {
statement: Statement::Available(candidate_b_digest.clone()),
signature: Signature(1),
sender: AuthorityId(1),
};
table.import_statement(&context, bad_availability_vote);
assert_eq!(
table.detected_misbehavior.get(&AuthorityId(1)).unwrap(),
&Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
statement: SignedStatement {
statement: Statement::Available(candidate_b_digest),
signature: Signature(1),
sender: AuthorityId(1),
},
})
);
// authority 2 votes for validity on 1's candidate. // authority 2 votes for validity on 1's candidate.
let bad_validity_vote = SignedStatement { let bad_validity_vote = SignedStatement {
statement: Statement::Valid(candidate_a_digest.clone()), statement: Statement::Valid(candidate_a_digest.clone()),
@@ -894,8 +775,8 @@ mod tests {
let context = TestContext { let context = TestContext {
authorities: { authorities: {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); map.insert(AuthorityId(1), GroupId(2));
map.insert(AuthorityId(2), (GroupId(2), GroupId(246))); map.insert(AuthorityId(2), GroupId(2));
map map
} }
}; };
@@ -943,8 +824,8 @@ mod tests {
let context = TestContext { let context = TestContext {
authorities: { authorities: {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); map.insert(AuthorityId(1), GroupId(2));
map.insert(AuthorityId(2), (GroupId(2), GroupId(246))); map.insert(AuthorityId(2), GroupId(2));
map map
} }
}; };
@@ -974,9 +855,9 @@ mod tests {
let context = TestContext { let context = TestContext {
authorities: { authorities: {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); map.insert(AuthorityId(1), GroupId(2));
map.insert(AuthorityId(2), (GroupId(2), GroupId(246))); map.insert(AuthorityId(2), GroupId(2));
map.insert(AuthorityId(3), (GroupId(2), GroupId(222))); map.insert(AuthorityId(3), GroupId(2));
map map
} }
}; };
@@ -1039,7 +920,7 @@ mod tests {
let context = TestContext { let context = TestContext {
authorities: { authorities: {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); map.insert(AuthorityId(1), GroupId(2));
map map
} }
}; };
@@ -1074,33 +955,25 @@ mod tests {
#[test] #[test]
fn candidate_can_be_included() { fn candidate_can_be_included() {
let validity_threshold = 6; let validity_threshold = 6;
let availability_threshold = 34;
let mut candidate = CandidateData::<TestContext> { let mut candidate = CandidateData::<TestContext> {
group_id: GroupId(4), group_id: GroupId(4),
candidate: Candidate(4, 12345), candidate: Candidate(4, 12345),
validity_votes: HashMap::new(), validity_votes: HashMap::new(),
availability_votes: HashMap::new(),
indicated_bad_by: Vec::new(), indicated_bad_by: Vec::new(),
}; };
assert!(!candidate.can_be_included(validity_threshold, availability_threshold)); assert!(!candidate.can_be_included(validity_threshold));
for i in 0..validity_threshold { for i in 0..validity_threshold {
candidate.validity_votes.insert(AuthorityId(i + 100), ValidityVote::Valid(Signature(i + 100))); candidate.validity_votes.insert(AuthorityId(i + 100), ValidityVote::Valid(Signature(i + 100)));
} }
assert!(!candidate.can_be_included(validity_threshold, availability_threshold)); assert!(candidate.can_be_included(validity_threshold));
for i in 0..availability_threshold {
candidate.availability_votes.insert(AuthorityId(i + 255), Signature(i + 255));
}
assert!(candidate.can_be_included(validity_threshold, availability_threshold));
candidate.indicated_bad_by.push(AuthorityId(1024)); candidate.indicated_bad_by.push(AuthorityId(1024));
assert!(!candidate.can_be_included(validity_threshold, availability_threshold)); assert!(!candidate.can_be_included(validity_threshold));
} }
#[test] #[test]
@@ -1108,10 +981,9 @@ mod tests {
let context = TestContext { let context = TestContext {
authorities: { authorities: {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); map.insert(AuthorityId(1), GroupId(2));
map.insert(AuthorityId(2), (GroupId(2), GroupId(455))); map.insert(AuthorityId(2), GroupId(2));
map.insert(AuthorityId(3), (GroupId(2), GroupId(455))); map.insert(AuthorityId(3), GroupId(2));
map.insert(AuthorityId(4), (GroupId(455), GroupId(2)));
map map
} }
}; };
@@ -1126,6 +998,7 @@ mod tests {
let candidate_digest = Digest(100); let candidate_digest = Digest(100);
table.import_statement(&context, statement); table.import_statement(&context, statement);
assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
assert!(!table.candidate_includable(&candidate_digest, &context)); assert!(!table.candidate_includable(&candidate_digest, &context));
assert!(table.includable_count.is_empty()); assert!(table.includable_count.is_empty());
@@ -1138,18 +1011,6 @@ mod tests {
table.import_statement(&context, vote); table.import_statement(&context, vote);
assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2)));
assert!(!table.candidate_includable(&candidate_digest, &context));
assert!(table.includable_count.is_empty());
// have the availability guarantor note validity.
let vote = SignedStatement {
statement: Statement::Available(candidate_digest.clone()),
signature: Signature(4),
sender: AuthorityId(4),
};
table.import_statement(&context, vote);
assert!(!table.detected_misbehavior.contains_key(&AuthorityId(4)));
assert!(table.candidate_includable(&candidate_digest, &context)); assert!(table.candidate_includable(&candidate_digest, &context));
assert!(table.includable_count.get(&GroupId(2)).is_some()); assert!(table.includable_count.get(&GroupId(2)).is_some());
@@ -1161,7 +1022,7 @@ mod tests {
}; };
table.import_statement(&context, vote); table.import_statement(&context, vote);
assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(3)));
assert!(!table.candidate_includable(&candidate_digest, &context)); assert!(!table.candidate_includable(&candidate_digest, &context));
assert!(table.includable_count.is_empty()); assert!(table.includable_count.is_empty());
} }
@@ -1171,7 +1032,7 @@ mod tests {
let context = TestContext { let context = TestContext {
authorities: { authorities: {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); map.insert(AuthorityId(1), GroupId(2));
map map
} }
}; };
@@ -1189,7 +1050,6 @@ mod tests {
assert_eq!(summary.candidate, Digest(100)); assert_eq!(summary.candidate, Digest(100));
assert_eq!(summary.group_id, GroupId(2)); assert_eq!(summary.group_id, GroupId(2));
assert_eq!(summary.validity_votes, 1); assert_eq!(summary.validity_votes, 1);
assert_eq!(summary.availability_votes, 0);
} }
#[test] #[test]
@@ -1197,8 +1057,8 @@ mod tests {
let context = TestContext { let context = TestContext {
authorities: { authorities: {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); map.insert(AuthorityId(1), GroupId(2));
map.insert(AuthorityId(2), (GroupId(2), GroupId(455))); map.insert(AuthorityId(2), GroupId(2));
map map
} }
}; };
@@ -1228,45 +1088,5 @@ mod tests {
assert_eq!(summary.candidate, Digest(100)); assert_eq!(summary.candidate, Digest(100));
assert_eq!(summary.group_id, GroupId(2)); assert_eq!(summary.group_id, GroupId(2));
assert_eq!(summary.validity_votes, 2); assert_eq!(summary.validity_votes, 2);
assert_eq!(summary.availability_votes, 0);
}
#[test]
fn availability_vote_gives_summary() {
let context = TestContext {
authorities: {
let mut map = HashMap::new();
map.insert(AuthorityId(1), (GroupId(2), GroupId(455)));
map.insert(AuthorityId(2), (GroupId(5), GroupId(2)));
map
}
};
let mut table = create();
let statement = SignedStatement {
statement: Statement::Candidate(Candidate(2, 100)),
signature: Signature(1),
sender: AuthorityId(1),
};
let candidate_digest = Digest(100);
table.import_statement(&context, statement);
assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1)));
let vote = SignedStatement {
statement: Statement::Available(candidate_digest.clone()),
signature: Signature(2),
sender: AuthorityId(2),
};
let summary = table.import_statement(&context, vote)
.expect("candidate vote to give summary");
assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2)));
assert_eq!(summary.candidate, Digest(100));
assert_eq!(summary.group_id, GroupId(2));
assert_eq!(summary.validity_votes, 1);
assert_eq!(summary.availability_votes, 1);
} }
} }
+3 -17
View File
@@ -48,17 +48,8 @@ pub trait Context {
/// Members are meant to submit candidates and vote on validity. /// Members are meant to submit candidates and vote on validity.
fn is_member_of(&self, authority: &SessionKey, group: &Id) -> bool; fn is_member_of(&self, authority: &SessionKey, group: &Id) -> bool;
/// Whether a authority is an availability guarantor of a group. // requisite number of votes for validity from a group.
/// Guarantors are meant to vote on availability for candidates submitted fn requisite_votes(&self, group: &Id) -> usize;
/// in a group.
fn is_availability_guarantor_of(
&self,
authority: &SessionKey,
group: &Id,
) -> bool;
// requisite number of votes for validity and availability respectively from a group.
fn requisite_votes(&self, group: &Id) -> (usize, usize);
} }
impl<C: Context> generic::Context for C { impl<C: Context> generic::Context for C {
@@ -80,11 +71,7 @@ impl<C: Context> generic::Context for C {
Context::is_member_of(self, authority, group) Context::is_member_of(self, authority, group)
} }
fn is_availability_guarantor_of(&self, authority: &SessionKey, group: &Id) -> bool { fn requisite_votes(&self, group: &Id) -> usize {
Context::is_availability_guarantor_of(self, authority, group)
}
fn requisite_votes(&self, group: &Id) -> (usize, usize) {
Context::requisite_votes(self, group) Context::requisite_votes(self, group)
} }
} }
@@ -95,7 +82,6 @@ impl From<Statement> for PrimitiveStatement {
generic::Statement::Valid(s) => PrimitiveStatement::Valid(s), generic::Statement::Valid(s) => PrimitiveStatement::Valid(s),
generic::Statement::Invalid(s) => PrimitiveStatement::Invalid(s), generic::Statement::Invalid(s) => PrimitiveStatement::Invalid(s),
generic::Statement::Candidate(s) => PrimitiveStatement::Candidate(s), generic::Statement::Candidate(s) => PrimitiveStatement::Candidate(s),
generic::Statement::Available(s) => PrimitiveStatement::Available(s),
} }
} }
} }