mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 15:11:02 +00:00
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:
committed by
Gav Wood
parent
7ac1fe9c01
commit
32cef97681
@@ -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);
|
||||
|
||||
@@ -145,7 +142,7 @@ 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(),
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
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() },
|
||||
);
|
||||
|
||||
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 candidate_hash = candidate.hash();
|
||||
|
||||
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,
|
||||
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),
|
||||
};
|
||||
|
||||
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));
|
||||
|
||||
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) {
|
||||
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: candidate_hash(),
|
||||
candidate_hash,
|
||||
block_data: block.clone(),
|
||||
extrinsic: extrinsic.clone(),
|
||||
extrinsic: Some(extrinsic.clone()),
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(Async::Ready(::std::mem::replace(statements, Default::default())))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
let signed = table.sign_and_import(produced.validity);
|
||||
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)
|
||||
);
|
||||
}
|
||||
})
|
||||
.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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}));
|
||||
} else {
|
||||
candidate.availability_votes.push((authorities[idx], 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());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user