diff --git a/polkadot/consensus/src/collation.rs b/polkadot/consensus/src/collation.rs index 33fcb63628deb04878c4e5a5f7b5ed41331daddb..562e48d2ca7e43a91e12a5a33bd2236638654c5c 100644 --- a/polkadot/consensus/src/collation.rs +++ b/polkadot/consensus/src/collation.rs @@ -100,10 +100,7 @@ impl<C: Collators, P: ProvideRuntimeApi> Future for CollationFetch<C, P> }; match validate_collation(&*self.client, &self.relay_parent, &x) { - Ok(()) => { - // TODO: generate extrinsic while verifying. - return Ok(Async::Ready((x, Extrinsic))); - } + Ok(e) => return Ok(Async::Ready((x, e))), Err(e) => { debug!("Failed to validate parachain due to API error: {}", e); @@ -140,12 +137,12 @@ error_chain! { } } -/// Check whether a given collation is valid. Returns `Ok` on success, error otherwise. +/// Check whether a given collation is valid. Returns `Ok` on success, error otherwise. pub fn validate_collation<P>( client: &P, relay_parent: &BlockId, collation: &Collation -) -> Result<(), Error> where +) -> Result<Extrinsic, Error> where P: ProvideRuntimeApi, P::Api: ParachainHost<Block> { @@ -167,7 +164,7 @@ pub fn validate_collation<P>( match parachain::wasm::validate_candidate(&validation_code, params) { Ok(result) => { if result.head_data == collation.receipt.head_data.0 { - Ok(()) + Ok(Extrinsic) } else { Err(ErrorKind::WrongHeadData( collation.receipt.head_data.0.clone(), diff --git a/polkadot/consensus/src/lib.rs b/polkadot/consensus/src/lib.rs index 096a4b214b31f4b52e390d6ac60f931fd2a2fce9..2d4876db207286966c88a1f5ebe230748c7fb816 100644 --- a/polkadot/consensus/src/lib.rs +++ b/polkadot/consensus/src/lib.rs @@ -92,7 +92,7 @@ use dynamic_inclusion::DynamicInclusion; pub use self::collation::{validate_collation, Collators}; 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 dynamic_inclusion; @@ -147,12 +147,8 @@ pub trait Network { pub struct GroupInfo { /// Authorities meant to check validity of candidates. pub validity_guarantors: HashSet<SessionKey>, - /// Authorities meant to check availability of candidate data. - pub availability_guarantors: HashSet<SessionKey>, /// Number of votes needed for validity. pub needed_validity: usize, - /// Number of votes needed for availability. - pub needed_availability: usize, } /// 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())) } - if roster.guarantor_duty.len() != authorities.len() { - bail!(ErrorKind::InvalidDutyRosterLength(authorities.len(), roster.guarantor_duty.len())) - } - let mut local_validation = None; let mut map = HashMap::new(); - let duty_iter = authorities.iter().zip(&roster.validator_duty).zip(&roster.guarantor_duty); - for ((authority, v_duty), a_duty) in duty_iter { + let duty_iter = authorities.iter().zip(&roster.validator_duty); + for (authority, v_duty) in duty_iter { if authority == &local_id { local_validation = Some(v_duty.clone()); } @@ -204,23 +196,11 @@ fn make_group_info(roster: DutyRoster, authorities: &[AuthorityId], local_id: Au .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() { 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_availability = availability_len / 2 + availability_len % 2; } match local_validation { @@ -470,8 +450,11 @@ fn dispatch_collation_work<R, C, P>( }); match res { - Ok(()) => - router.local_candidate(collation.receipt, collation.block_data, extrinsic), + Ok(()) => { + // 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) => warn!(target: "consensus", "Failed to make collation data available: {:?}", e), } diff --git a/polkadot/consensus/src/shared_table/mod.rs b/polkadot/consensus/src/shared_table/mod.rs index 34087566214a67738fcdbbdf2f266f26542c2bd5..a74eae8ea6f49f92161b46435ef1f97209f8ad1c 100644 --- a/polkadot/consensus/src/shared_table/mod.rs +++ b/polkadot/consensus/src/shared_table/mod.rs @@ -22,18 +22,19 @@ use std::sync::Arc; use extrinsic_store::{Data, Store as ExtrinsicStore}; use table::{self, Table, Context as TableContextTrait}; -use polkadot_primitives::{Hash, SessionKey}; +use polkadot_primitives::{Block, BlockId, Hash, SessionKey}; use polkadot_primitives::parachain::{ Id as ParaId, BlockData, Collation, Extrinsic, CandidateReceipt, - AttestedCandidate, + AttestedCandidate, ParachainHost }; use parking_lot::Mutex; -use futures::{future, prelude::*}; +use futures::prelude::*; use super::{GroupInfo, TableRouter}; use self::includable::IncludabilitySender; use primitives::ed25519; +use runtime_primitives::{traits::ProvideRuntimeApi}; mod includable; @@ -52,15 +53,8 @@ impl table::Context for TableContext { self.groups.get(group).map_or(false, |g| g.validity_guarantors.contains(authority)) } - fn is_availability_guarantor_of(&self, authority: &SessionKey, group: &ParaId) -> bool { - self.groups.get(group).map_or(false, |g| g.availability_guarantors.contains(authority)) - } - - 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), - ) + fn requisite_votes(&self, group: &ParaId) -> usize { + self.groups.get(group).map_or(usize::max_value(), |g| g.needed_validity) } } @@ -85,7 +79,6 @@ struct SharedTableInner { table: Table<TableContext>, proposed_digest: Option<Hash>, checked_validity: HashSet<Hash>, - checked_availability: HashSet<Hash>, trackers: Vec<IncludabilitySender>, extrinsic_store: ExtrinsicStore, } @@ -101,9 +94,8 @@ impl SharedTableInner { context: &TableContext, router: &R, statement: table::SignedStatement, - ) -> Option<StatementProducer< + ) -> Option<ParachainWork< <R::FetchCandidate as IntoFuture>::Future, - <R::FetchExtrinsic as IntoFuture>::Future, >> { let summary = match self.table.import_statement(context, statement) { Some(summary) => summary, @@ -114,42 +106,25 @@ impl SharedTableInner { let local_id = context.local_id(); - let is_validity_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 para_member = context.is_member_of(&local_id, &summary.group_id); let digest = &summary.candidate; // TODO: consider a strategy based on the number of candidate votes as well. // 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.checked_validity.insert(digest.clone()); - let checking_availability = is_availability_member - && self.checked_availability.insert(digest.clone()); - - let work = if checking_validity || checking_availability { + let work = if extra_work { match self.table.get_candidate(&digest) { None => None, // TODO: handle table inconsistency somehow? Some(candidate) => { - let fetch_block_data = - 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 - }; + let fetch_block_data = router.fetch_block_data(candidate).into_future(); Some(Work { candidate_receipt: candidate.clone(), fetch_block_data, - fetch_extrinsic, - evaluate: checking_validity, - ensure_available: checking_availability, }) } } @@ -157,8 +132,7 @@ impl SharedTableInner { None }; - work.map(|work| StatementProducer { - produced_statements: Default::default(), + work.map(|work| ParachainWork { extrinsic_store: self.extrinsic_store.clone(), relay_parent: context.parent_hash.clone(), work @@ -175,130 +149,114 @@ impl SharedTableInner { } } -/// Produced statements about a specific candidate. -/// Both may be `None`. -#[derive(Default)] -pub struct ProducedStatements { +/// Produced after validating a candidate. +pub struct Validated { /// A statement about the validity of the candidate. - pub validity: Option<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>, + pub validity: table::Statement, /// Block data to ensure availability of. - pub block_data: Option<BlockData>, + pub block_data: BlockData, /// Extrinsic data to ensure availability of. - pub extrinsic: Option<Extrinsic>, + pub extrinsic: Extrinsic, } -/// Future that produces statements about a specific candidate. -pub struct StatementProducer<D: Future, E: Future> { - produced_statements: ProducedStatements, - work: Work<D, E>, +/// Future that performs parachain validation work. +pub struct ParachainWork<D: Future> { + work: Work<D>, relay_parent: Hash, extrinsic_store: ExtrinsicStore, } -impl<D: Future, E: Future> StatementProducer<D, E> { - /// Attach a function for verifying fetched collation to the statement producer. - /// This will transform it into a future. - /// - /// The collation-checking function should return `true` if known to be valid, - /// `false` if known to be invalid, and `None` if unable to determine. - pub fn prime<C: FnMut(Collation) -> Option<bool>>(self, check_candidate: C) -> PrimedStatementProducer<D, E, C> { - PrimedStatementProducer { - inner: self, - check_candidate, - } +impl<D: Future> ParachainWork<D> { + /// Prime the parachain work with an API reference for extracting + /// chain information. + pub fn prime<P: ProvideRuntimeApi>(self, api: Arc<P>) + -> PrimedParachainWork< + D, + impl Send + FnMut(&BlockId, &Collation) -> bool, + > + where + 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, - fetch_block_data: future::Fuse<D>, - fetch_extrinsic: Option<future::Fuse<E>>, - evaluate: bool, - ensure_available: bool, + fetch_block_data: D, } /// Primed statement producer. -pub struct PrimedStatementProducer<D: Future, E: Future, C> { - inner: StatementProducer<D, E>, - check_candidate: C, +pub struct PrimedParachainWork<D: Future, F> { + inner: ParachainWork<D>, + validate: F, } -impl<D, E, C, Err> Future for PrimedStatementProducer<D, E, C> +impl<D, F, Err> Future for PrimedParachainWork<D, F> where D: Future<Item=BlockData,Error=Err>, - E: Future<Item=Extrinsic,Error=Err>, - C: FnMut(Collation) -> Option<bool>, + F: FnMut(&BlockId, &Collation) -> bool, Err: From<::std::io::Error>, { - type Item = ProducedStatements; + type Item = Validated; type Error = Err; - fn poll(&mut self) -> Poll<ProducedStatements, Err> { + fn poll(&mut self) -> Poll<Validated, Err> { let work = &mut self.inner.work; let candidate = &work.candidate_receipt; - let statements = &mut self.inner.produced_statements; - - let mut candidate_hash = None; - let mut candidate_hash = move || - candidate_hash.get_or_insert_with(|| candidate.hash()).clone(); - - if let Async::Ready(block_data) = work.fetch_block_data.poll()? { - 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? {:?}", hash, is_good); - statements.validity = match is_good { - Some(true) => Some(GenericStatement::Valid(hash)), - Some(false) => Some(GenericStatement::Invalid(hash)), - None => None, - }; - - work.evaluate = false; - } - } - - 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)); + let block = try_ready!(work.fetch_block_data.poll()); + let is_good = (self.validate)( + &BlockId::hash(self.inner.relay_parent), + &Collation { block_data: block.clone(), receipt: candidate.clone() }, + ); - work.ensure_available = false; - } - } + let candidate_hash = candidate.hash(); - let done = match (work.evaluate, work.ensure_available) { - (false, false) => true, - _ => false, + debug!(target: "consensus", "Making validity statement about candidate {}: is_good? {:?}", candidate_hash, is_good); + let validity_statement = match is_good { + true => GenericStatement::Valid(candidate_hash), + false => GenericStatement::Invalid(candidate_hash), }; - 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 { - relay_parent: self.inner.relay_parent, - parachain_id: work.candidate_receipt.parachain_index, - candidate_hash: candidate_hash(), - block_data: block.clone(), - extrinsic: extrinsic.clone(), - })?; - } - - Ok(Async::Ready(::std::mem::replace(statements, Default::default()))) - } else { - Ok(Async::NotReady) - } + let extrinsic = Extrinsic; + self.inner.extrinsic_store.make_available(Data { + relay_parent: self.inner.relay_parent, + parachain_id: work.candidate_receipt.parachain_index, + candidate_hash, + block_data: block.clone(), + extrinsic: Some(extrinsic.clone()), + })?; + + Ok(Async::Ready(Validated { + validity: validity_statement, + block_data: block, + extrinsic, + })) } } @@ -334,7 +292,6 @@ impl SharedTable { table: Table::default(), proposed_digest: None, checked_validity: HashSet::new(), - checked_availability: HashSet::new(), trackers: Vec::new(), extrinsic_store, })) @@ -364,9 +321,8 @@ impl SharedTable { &self, router: &R, statement: table::SignedStatement, - ) -> Option<StatementProducer< + ) -> Option<ParachainWork< <R::FetchCandidate as IntoFuture>::Future, - <R::FetchExtrinsic as IntoFuture>::Future, >> { self.inner.lock().import_remote_statement(&*self.context, router, statement) } @@ -381,9 +337,8 @@ impl SharedTable { where R: TableRouter, I: IntoIterator<Item=table::SignedStatement>, - U: ::std::iter::FromIterator<Option<StatementProducer< + U: ::std::iter::FromIterator<Option<ParachainWork< <R::FetchCandidate as IntoFuture>::Future, - <R::FetchExtrinsic as IntoFuture>::Future, >>>, { let mut inner = self.inner.lock(); @@ -394,25 +349,12 @@ impl SharedTable { } /// 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) - -> (SignedStatement, Option<SignedStatement>) + -> SignedStatement { - let (proposed_digest, availability) = match statement { - GenericStatement::Candidate(ref c) => { - let mut availability = 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 proposed_digest = match statement { + GenericStatement::Candidate(ref c) => Some(c.hash()), + _ => None, }; let signed_statement = self.context.sign_statement(statement); @@ -424,12 +366,7 @@ impl SharedTable { inner.table.import_statement(&*self.context, signed_statement.clone()); - // ensure the availability statement is imported after the candidate. - if let Some(a) = availability.clone() { - inner.table.import_statement(&*self.context, a); - } - - (signed_statement, availability) + signed_statement } /// Execute a closure using a specific candidate. @@ -454,7 +391,6 @@ impl SharedTable { table_attestations.into_iter() .map(|attested| AttestedCandidate { candidate: attested.candidate, - availability_votes: attested.availability_votes, validity_votes: attested.validity_votes.into_iter().map(|(a, v)| match v { GAttestation::Implicit(s) => (a, ValidityAttestation::Implicit(s)), GAttestation::Explicit(s) => (a, ValidityAttestation::Explicit(s)), @@ -468,7 +404,7 @@ impl SharedTable { 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 { self.inner.lock().table.includable_count() } @@ -501,22 +437,23 @@ impl SharedTable { mod tests { use super::*; use substrate_keyring::Keyring; + use futures::future; #[derive(Clone)] struct DummyRouter; impl TableRouter for DummyRouter { type Error = ::std::io::Error; - type FetchCandidate = ::futures::future::Empty<BlockData,Self::Error>; - type FetchExtrinsic = ::futures::future::Empty<Extrinsic,Self::Error>; + type FetchCandidate = ::futures::future::FutureResult<BlockData,Self::Error>; + type FetchExtrinsic = ::futures::future::FutureResult<Extrinsic,Self::Error>; fn local_candidate(&self, _candidate: CandidateReceipt, _block_data: BlockData, _extrinsic: Extrinsic) { } 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 { - ::futures::future::empty() + future::ok(Extrinsic) } } @@ -534,9 +471,7 @@ mod tests { groups.insert(para_id, GroupInfo { validity_guarantors: [local_id, validity_other].iter().cloned().collect(), - availability_guarantors: Default::default(), needed_validity: 2, - needed_availability: 0, }); let shared_table = SharedTable::new( @@ -566,17 +501,14 @@ mod tests { sender: validity_other, }; - let producer = shared_table.import_remote_statement( + shared_table.import_remote_statement( &DummyRouter, signed_statement, ).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] - fn statement_triggers_fetch_and_availability() { + fn statement_triggers_fetch_and_validity() { let mut groups = HashMap::new(); let para_id = ParaId::from(1); @@ -588,10 +520,8 @@ mod tests { let parent_hash = Default::default(); groups.insert(para_id, GroupInfo { - validity_guarantors: [validity_other].iter().cloned().collect(), - availability_guarantors: [local_id].iter().cloned().collect(), + validity_guarantors: [local_id, validity_other].iter().cloned().collect(), needed_validity: 1, - needed_availability: 1, }); let shared_table = SharedTable::new( @@ -621,14 +551,10 @@ mod tests { sender: validity_other, }; - let producer = shared_table.import_remote_statement( + shared_table.import_remote_statement( &DummyRouter, signed_statement, ).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] @@ -651,28 +577,22 @@ mod tests { let hash = candidate.hash(); - let block_data_res: ::std::io::Result<_> = Ok(block_data.clone()); - let producer: StatementProducer<_, future::Empty<_, _>> = StatementProducer { - produced_statements: Default::default(), + let producer: ParachainWork<future::FutureResult<_, ::std::io::Error>> = ParachainWork { work: Work { candidate_receipt: candidate, - fetch_block_data: block_data_res.into_future().fuse(), - fetch_extrinsic: None, - evaluate: true, - ensure_available: false, + fetch_block_data: future::ok(block_data.clone()), }, relay_parent, 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!(produced.validity.is_some()); - assert!(produced.availability.is_none()); + assert_eq!(produced.block_data, block_data); + assert_eq!(produced.validity, GenericStatement::Valid(hash)); 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] @@ -695,26 +615,18 @@ mod tests { let hash = candidate.hash(); - let block_data_res: ::std::io::Result<_> = Ok(block_data.clone()); - let extrinsic_res: ::std::io::Result<_> = Ok(Extrinsic); - let producer = StatementProducer { - produced_statements: Default::default(), + let producer = ParachainWork { work: Work { candidate_receipt: candidate, - fetch_block_data: block_data_res.into_future().fuse(), - fetch_extrinsic: Some(extrinsic_res.into_future().fuse()), - evaluate: false, - ensure_available: true, + fetch_block_data: future::ok::<_, ::std::io::Error>(block_data.clone()), }, relay_parent, 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!(produced.validity.is_none()); - assert!(produced.availability.is_some()); + assert_eq!(produced.block_data, block_data); assert_eq!(store.block_data(relay_parent, hash).unwrap(), block_data); assert!(store.extrinsic(relay_parent, hash).is_some()); diff --git a/polkadot/network/src/consensus.rs b/polkadot/network/src/consensus.rs index 04263afed0f3e6051a33951a0e5ecf6860acb00c..75f69819b66b0b4ac7f20d828b007ecc8f1de23e 100644 --- a/polkadot/network/src/consensus.rs +++ b/polkadot/network/src/consensus.rs @@ -237,11 +237,6 @@ impl Knowledge { entry.knows_block_data.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) .or_insert_with(Default::default) .knows_block_data diff --git a/polkadot/network/src/router.rs b/polkadot/network/src/router.rs index 205aff1e65e615f8156765338c462e2fa1d44f47..58d0c38500b3aa79de01679af8df6d5fcab044e0 100644 --- a/polkadot/network/src/router.rs +++ b/polkadot/network/src/router.rs @@ -23,8 +23,8 @@ //! and dispatch evaluation work as necessary when new statements come in. use sr_primitives::traits::{ProvideRuntimeApi, BlakeTwo256, Hash as HashT}; -use polkadot_consensus::{SharedTable, TableRouter, SignedStatement, GenericStatement, StatementProducer}; -use polkadot_primitives::{Block, Hash, BlockId, SessionKey}; +use polkadot_consensus::{SharedTable, TableRouter, SignedStatement, GenericStatement, ParachainWork}; +use polkadot_primitives::{Block, Hash, SessionKey}; use polkadot_primitives::parachain::{BlockData, Extrinsic, CandidateReceipt, ParachainHost}; use codec::Encode; @@ -115,7 +115,6 @@ impl<P: ProvideRuntimeApi + Send + Sync + 'static> Router<P> GenericStatement::Candidate(ref c) => Some(c.hash()), GenericStatement::Valid(ref hash) | GenericStatement::Invalid(ref hash) - | GenericStatement::Available(ref hash) => self.table.with_candidate(hash, |c| c.map(|_| *hash)), }; 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=()> where 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 network = self.network.clone(); let knowledge = self.knowledge.clone(); let attestation_topic = self.attestation_topic.clone(); - producer.prime(validate) + producer.prime(self.api.clone()) .map(move |produced| { // store the data before broadcasting statements, so other peers can fetch. knowledge.lock().note_candidate( candidate_hash, - produced.block_data, - produced.extrinsic, + Some(produced.block_data), + Some(produced.extrinsic), ); - if produced.validity.is_none() && produced.availability.is_none() { - return - } - let mut gossip = network.consensus_gossip().write(); - // propagate the statements + // propagate the statement. // consider something more targeted than gossip in the future. - if let Some(validity) = produced.validity { - let signed = table.sign_and_import(validity.clone()).0; - network.with_spec(|_, ctx| - 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) - ); - } + let signed = table.sign_and_import(produced.validity); + network.with_spec(|_, ctx| + gossip.multicast(ctx, attestation_topic, signed.encode(), false) + ); }) .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) { // give to network to make available. 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)); let mut gossip = self.network.consensus_gossip().write(); self.network.with_spec(|_spec, ctx| { 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 { Valid(SessionKey, Hash), Invalid(SessionKey, Hash), - Available(SessionKey, Hash), } // helper for deferring statements whose associated candidate is unknown. @@ -296,7 +263,6 @@ impl DeferredStatements { GenericStatement::Candidate(_) => return, GenericStatement::Valid(hash) => (hash, StatementTrace::Valid(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) { @@ -314,7 +280,6 @@ impl DeferredStatements { GenericStatement::Candidate(_) => continue, GenericStatement::Valid(hash) => StatementTrace::Valid(statement.sender, hash), GenericStatement::Invalid(hash) => StatementTrace::Invalid(statement.sender, hash), - GenericStatement::Available(hash) => StatementTrace::Available(statement.sender, hash), }; self.known_traces.remove(&trace); diff --git a/polkadot/parachain/src/lib.rs b/polkadot/parachain/src/lib.rs index 7c105c6f99068fa5ecdf50ba2bf9079819588b62..e81df3a967f6a0ab09cd689ff29be8a6da4b5987 100644 --- a/polkadot/parachain/src/lib.rs +++ b/polkadot/parachain/src/lib.rs @@ -86,7 +86,7 @@ pub struct ValidationParams { #[cfg_attr(feature = "std", derive(Debug))] pub struct ValidationResult { /// 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. diff --git a/polkadot/primitives/src/parachain.rs b/polkadot/primitives/src/parachain.rs index 2e209011ab73504b89a9e287e67fcb25789653c0..d2bc3b46b65fad297483059cfd5fd9864c1d38a4 100644 --- a/polkadot/primitives/src/parachain.rs +++ b/polkadot/primitives/src/parachain.rs @@ -64,9 +64,6 @@ pub enum Chain { pub struct DutyRoster { /// Lookup from validator index to chain on which that validator has a duty to validate. 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. @@ -206,9 +203,6 @@ pub enum Statement { /// State a candidate is invalid. #[codec(index = "3")] 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 @@ -234,8 +228,6 @@ pub struct AttestedCandidate { pub candidate: CandidateReceipt, /// Validity attestations. pub validity_votes: Vec<(SessionKey, ValidityAttestation)>, - /// Availability attestations. - pub availability_votes: Vec<(SessionKey, CandidateSignature)>, } impl AttestedCandidate { diff --git a/polkadot/runtime/src/parachains.rs b/polkadot/runtime/src/parachains.rs index b4256c4001de0480a4908be476cdec80fe25e231..a1e24adb8407cfe7d0be4bcd69bb1588bbe32ba3 100644 --- a/polkadot/runtime/src/parachains.rs +++ b/polkadot/runtime/src/parachains.rs @@ -196,8 +196,6 @@ impl<T: Trait> Module<T> { _ => Chain::Relay, }).collect::<Vec<_>>(); - let mut roles_gua = roles_val.clone(); - let mut random_seed = system::Module::<T>::random_seed().as_ref().to_vec(); random_seed.extend(b"validator_role_pairs"); 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. 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 { // 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. roles_val.swap(remaining - 1, val_index); - roles_gua.swap(remaining - 1, gua_index); } DutyRoster { 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_guarantors = make_sorted_duties(&duty_roster.guarantor_duty); let parent_hash = super::System::parent_hash(); let localized_payload = |statement: Statement| localized_payload(statement, parent_hash); let mut validator_groups = GroupedDutyIter::new(&sorted_validators[..]); - let mut guarantor_groups = GroupedDutyIter::new(&sorted_guarantors[..]); for candidate in attested_candidates { let validator_group = validator_groups.group_for(candidate.parachain_index()) .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!( candidate.validity_votes.len() >= majority_of(validator_group.len()), "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 encoded_implicit = None; let mut encoded_explicit = None; - // track which voters have voted already. the first `authorities.len()` - // bits is for validity, the next are for availability. - let mut track_voters = bitvec![0; authorities.len() * 2]; + // track which voters have voted already, 1 bit per authority. + let mut track_voters = bitvec![0; authorities.len()]; for (auth_id, validity_attestation) in &candidate.validity_votes { // protect against double-votes. 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." ); } - - 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(()) @@ -544,39 +502,28 @@ mod tests { }; let validation_entries = duty_roster.validator_duty.iter() - .enumerate() - .map(|(i, d)| (i, d, true)); + .enumerate(); - let availability_entries = duty_roster.guarantor_duty.iter() - .enumerate() - .map(|(i, d)| (i, d, false)); - - for (idx, &duty, is_validation) in validation_entries.chain(availability_entries) { + for (idx, &duty) in validation_entries { 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 statement = if is_validation && vote_implicit { + let statement = if vote_implicit { Statement::Candidate(candidate.candidate.clone()) - } else if is_validation { - Statement::Valid(candidate_hash.clone()) } else { - Statement::Available(candidate_hash.clone()) + Statement::Valid(candidate_hash.clone()) }; let payload = localized_payload(statement, parent_hash); let signature = key.sign(&payload[..]).into(); - if is_validation { - candidate.validity_votes.push((authorities[idx], if vote_implicit { - ValidityAttestation::Implicit(signature) - } else { - ValidityAttestation::Explicit(signature) - })); + candidate.validity_votes.push((authorities[idx], if vote_implicit { + ValidityAttestation::Implicit(signature) } else { - candidate.availability_votes.push((authorities[idx], signature)); - } + ValidityAttestation::Explicit(signature) + })); } } @@ -629,13 +576,10 @@ mod tests { with_externalities(&mut new_test_ext(parachains), || { let check_roster = |duty_roster: &DutyRoster| { assert_eq!(duty_roster.validator_duty.len(), 8); - assert_eq!(duty_roster.guarantor_duty.len(), 8); 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.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.guarantor_duty.iter().filter(|&&j| j == Chain::Relay).count(), 2); }; system::Module::<Test>::set_random_seed([0u8; 32].into()); @@ -667,7 +611,6 @@ mod tests { system::Module::<Test>::set_random_seed([0u8; 32].into()); let candidate = AttestedCandidate { validity_votes: vec![], - availability_votes: vec![], candidate: CandidateReceipt { parachain_index: 0.into(), collator: Default::default(), @@ -695,7 +638,6 @@ mod tests { system::Module::<Test>::set_random_seed([0u8; 32].into()); let mut candidate_a = AttestedCandidate { validity_votes: vec![], - availability_votes: vec![], candidate: CandidateReceipt { parachain_index: 0.into(), collator: Default::default(), @@ -710,7 +652,6 @@ mod tests { let mut candidate_b = AttestedCandidate { validity_votes: vec![], - availability_votes: vec![], candidate: CandidateReceipt { parachain_index: 1.into(), collator: Default::default(), @@ -749,7 +690,6 @@ mod tests { system::Module::<Test>::set_random_seed([0u8; 32].into()); let mut candidate = AttestedCandidate { validity_votes: vec![], - availability_votes: vec![], candidate: CandidateReceipt { parachain_index: 0.into(), collator: Default::default(), @@ -771,14 +711,6 @@ mod tests { Call::set_heads(vec![double_validity]), Origin::INHERENT, ).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()); }); } } diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm index f8b4553ae7dab3b0efa8dbe48970f44b6ad91dd9..7cddf58035c1a0259acf8c8905617619cf9faba4 100644 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm differ diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm index 0bcff8ef9ced3b97c28c1ae43c85e7e7e2fb77fa..6566dee3b648003b6f7284ebd8a7653348100a84 100755 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm differ diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs index 6deece66df4a5c53e86f7bcd8443c6967c5b1eb0..2389efdbdf76c93d6b3815e79af852e3503bbcfd 100644 --- a/polkadot/statement-table/src/generic.rs +++ b/polkadot/statement-table/src/generic.rs @@ -20,12 +20,9 @@ //! //! 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 -//! validity and availability votes. -//! -//! 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. +//! Each parachain is associated with a committee of authorities, who issue statements +//! 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::hash::Hash; @@ -54,17 +51,8 @@ pub trait Context { /// Members are meant to submit candidates and vote on validity. fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool; - /// Whether a authority is an availability guarantor of a group. - /// Guarantors are meant to vote on availability for candidates submitted - /// 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); + // requisite number of votes for validity from a group. + fn requisite_votes(&self, group: &Self::GroupId) -> usize; } /// Statements circulated among peers. @@ -84,10 +72,6 @@ pub enum Statement<C, D> { /// is invalid. #[codec(index = "3")] 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. @@ -124,8 +108,6 @@ pub enum DoubleSign<C, D, S> { Validity(D, S, S), /// On invalidity. Invalidity(D, S, S), - /// On availability. - Availability(D, S, S), } /// Misbehavior: declaring multiple candidates. @@ -181,8 +163,6 @@ pub struct Summary<D, G> { pub group_id: G, /// How many validity votes are currently witnessed. 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. pub signalled_bad: bool, } @@ -207,8 +187,6 @@ pub struct AttestedCandidate<Group, Candidate, AuthorityId, Signature> { 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. @@ -216,7 +194,6 @@ pub struct CandidateData<C: Context> { group_id: C::GroupId, candidate: C::Candidate, validity_votes: HashMap<C::AuthorityId, ValidityVote<C::Signature>>, - availability_votes: HashMap<C::AuthorityId, C::Signature>, indicated_bad_by: Vec<C::AuthorityId>, } @@ -228,12 +205,12 @@ impl<C: Context> CandidateData<C> { /// 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) + pub fn attested(&self, validity_threshold: usize) -> Option<AttestedCandidate< 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() .filter_map(|(a, v)| match *v { ValidityVote::Invalid(_) => None, @@ -252,21 +229,10 @@ impl<C: Context> CandidateData<C> { "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 @@ -274,12 +240,11 @@ impl<C: Context> CandidateData<C> { } // 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. - 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.validity_votes.len() >= validity_threshold - && self.availability_votes.len() >= availability_threshold } fn summary(&self, digest: C::Digest) -> Summary<C::Digest, C::GroupId> { @@ -287,7 +252,6 @@ impl<C: Context> CandidateData<C> { candidate: digest, group_id: self.group_id.clone(), validity_votes: self.validity_votes.len() - self.indicated_bad_by.len(), - availability_votes: self.availability_votes.len(), signalled_bad: self.indicated_bad(), } } @@ -352,12 +316,12 @@ impl<C: Context> Table<C> { 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()) { BTreeEntry::Vacant(vacant) => { - vacant.insert((candidate_data, validity_t, availability_t)); + vacant.insert((candidate_data, threshold)); }, BTreeEntry::Occupied(mut occ) => { let candidate_ref = occ.get_mut(); @@ -369,8 +333,8 @@ impl<C: Context> Table<C> { } best_candidates.values() - .map(|&(candidate_data, validity_t, availability_t)| - candidate_data.attested(validity_t, availability_t) + .map(|&(candidate_data, threshold)| + candidate_data.attested(threshold) .expect("candidate has been checked includable; \ therefore an attestation can be constructed; qed") ) @@ -380,8 +344,8 @@ impl<C: Context> Table<C> { /// Whether a candidate can be included. pub fn candidate_includable(&self, digest: &C::Digest, context: &C) -> bool { self.candidate_votes.get(digest).map_or(false, |data| { - let (v_threshold, a_threshold) = context.requisite_votes(&data.group_id); - data.can_be_included(v_threshold, a_threshold) + let v_threshold = context.requisite_votes(&data.group_id); + data.can_be_included(v_threshold) }) } @@ -415,12 +379,6 @@ impl<C: Context> Table<C> { digest, ValidityVote::Invalid(signature), ), - Statement::Available(digest) => self.availability_vote( - context, - signer.clone(), - digest, - signature, - ), }; match res { @@ -517,7 +475,6 @@ impl<C: Context> Table<C> { group_id: group, candidate: candidate, validity_votes: HashMap::new(), - availability_votes: HashMap::new(), indicated_bad_by: Vec::new(), }); } @@ -542,8 +499,8 @@ impl<C: Context> Table<C> { 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); + let v_threshold = context.requisite_votes(&votes.group_id); + let was_includable = votes.can_be_included(v_threshold); // check that this authority actually can vote in this group. 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); - 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); + 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))) @@ -703,8 +621,8 @@ mod tests { #[derive(Debug, PartialEq, Eq)] struct TestContext { - // v -> (validity, availability) - authorities: HashMap<AuthorityId, (GroupId, GroupId)> + // v -> parachain group + authorities: HashMap<AuthorityId, GroupId> } impl Context for TestContext { @@ -727,27 +645,17 @@ mod tests { authority: &AuthorityId, group: &GroupId ) -> 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( - &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) { + fn requisite_votes(&self, id: &GroupId) -> usize { 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 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 { authorities: { let mut map = HashMap::new(); - map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); + map.insert(AuthorityId(1), GroupId(2)); map } }; @@ -792,7 +700,7 @@ mod tests { let context = TestContext { authorities: { let mut map = HashMap::new(); - map.insert(AuthorityId(1), (GroupId(3), GroupId(455))); + map.insert(AuthorityId(1), GroupId(3)); map } }; @@ -823,8 +731,8 @@ mod tests { let context = TestContext { authorities: { let mut map = HashMap::new(); - map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); - map.insert(AuthorityId(2), (GroupId(3), GroupId(222))); + map.insert(AuthorityId(1), GroupId(2)); + map.insert(AuthorityId(2), GroupId(3)); map } }; @@ -838,37 +746,10 @@ mod tests { }; 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_b); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); 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. let bad_validity_vote = SignedStatement { statement: Statement::Valid(candidate_a_digest.clone()), @@ -894,8 +775,8 @@ mod tests { let context = TestContext { authorities: { let mut map = HashMap::new(); - map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); - map.insert(AuthorityId(2), (GroupId(2), GroupId(246))); + map.insert(AuthorityId(1), GroupId(2)); + map.insert(AuthorityId(2), GroupId(2)); map } }; @@ -943,8 +824,8 @@ mod tests { let context = TestContext { authorities: { let mut map = HashMap::new(); - map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); - map.insert(AuthorityId(2), (GroupId(2), GroupId(246))); + map.insert(AuthorityId(1), GroupId(2)); + map.insert(AuthorityId(2), GroupId(2)); map } }; @@ -974,9 +855,9 @@ mod tests { let context = TestContext { authorities: { let mut map = HashMap::new(); - map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); - map.insert(AuthorityId(2), (GroupId(2), GroupId(246))); - map.insert(AuthorityId(3), (GroupId(2), GroupId(222))); + map.insert(AuthorityId(1), GroupId(2)); + map.insert(AuthorityId(2), GroupId(2)); + map.insert(AuthorityId(3), GroupId(2)); map } }; @@ -1039,7 +920,7 @@ mod tests { let context = TestContext { authorities: { let mut map = HashMap::new(); - map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); + map.insert(AuthorityId(1), GroupId(2)); map } }; @@ -1074,33 +955,25 @@ mod tests { #[test] fn candidate_can_be_included() { let validity_threshold = 6; - let availability_threshold = 34; let mut candidate = CandidateData::<TestContext> { group_id: GroupId(4), candidate: Candidate(4, 12345), validity_votes: HashMap::new(), - availability_votes: HashMap::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 { candidate.validity_votes.insert(AuthorityId(i + 100), ValidityVote::Valid(Signature(i + 100))); } - assert!(!candidate.can_be_included(validity_threshold, availability_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)); + assert!(candidate.can_be_included(validity_threshold)); candidate.indicated_bad_by.push(AuthorityId(1024)); - assert!(!candidate.can_be_included(validity_threshold, availability_threshold)); + assert!(!candidate.can_be_included(validity_threshold)); } #[test] @@ -1108,10 +981,9 @@ mod tests { let context = TestContext { authorities: { let mut map = HashMap::new(); - map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); - map.insert(AuthorityId(2), (GroupId(2), GroupId(455))); - map.insert(AuthorityId(3), (GroupId(2), GroupId(455))); - map.insert(AuthorityId(4), (GroupId(455), GroupId(2))); + map.insert(AuthorityId(1), GroupId(2)); + map.insert(AuthorityId(2), GroupId(2)); + map.insert(AuthorityId(3), GroupId(2)); map } }; @@ -1126,6 +998,7 @@ mod tests { let candidate_digest = Digest(100); table.import_statement(&context, statement); + assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); assert!(!table.candidate_includable(&candidate_digest, &context)); assert!(table.includable_count.is_empty()); @@ -1138,18 +1011,6 @@ mod tests { table.import_statement(&context, vote); 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.includable_count.get(&GroupId(2)).is_some()); @@ -1161,7 +1022,7 @@ mod tests { }; 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.includable_count.is_empty()); } @@ -1171,7 +1032,7 @@ mod tests { let context = TestContext { authorities: { let mut map = HashMap::new(); - map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); + map.insert(AuthorityId(1), GroupId(2)); map } }; @@ -1189,7 +1050,6 @@ mod tests { assert_eq!(summary.candidate, Digest(100)); assert_eq!(summary.group_id, GroupId(2)); assert_eq!(summary.validity_votes, 1); - assert_eq!(summary.availability_votes, 0); } #[test] @@ -1197,8 +1057,8 @@ mod tests { let context = TestContext { authorities: { let mut map = HashMap::new(); - map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); - map.insert(AuthorityId(2), (GroupId(2), GroupId(455))); + map.insert(AuthorityId(1), GroupId(2)); + map.insert(AuthorityId(2), GroupId(2)); map } }; @@ -1228,45 +1088,5 @@ mod tests { assert_eq!(summary.candidate, Digest(100)); assert_eq!(summary.group_id, GroupId(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); } } diff --git a/polkadot/statement-table/src/lib.rs b/polkadot/statement-table/src/lib.rs index 91173aad680e28a23790d1b8f5297c0097849ffe..15078446126dc649f7ae6dff7f7dd0ddca1eee85 100644 --- a/polkadot/statement-table/src/lib.rs +++ b/polkadot/statement-table/src/lib.rs @@ -48,17 +48,8 @@ pub trait Context { /// Members are meant to submit candidates and vote on validity. fn is_member_of(&self, authority: &SessionKey, group: &Id) -> bool; - /// Whether a authority is an availability guarantor of a group. - /// Guarantors are meant to vote on availability for candidates submitted - /// 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); + // requisite number of votes for validity from a group. + fn requisite_votes(&self, group: &Id) -> usize; } 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) } - fn is_availability_guarantor_of(&self, authority: &SessionKey, group: &Id) -> bool { - Context::is_availability_guarantor_of(self, authority, group) - } - - fn requisite_votes(&self, group: &Id) -> (usize, usize) { + fn requisite_votes(&self, group: &Id) -> usize { Context::requisite_votes(self, group) } } @@ -95,7 +82,6 @@ impl From<Statement> for PrimitiveStatement { 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), } } }