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