// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see .
//! Parachain statement table meant to be shared with a message router
//! and a consensus proposer.
use std::collections::hash_map::{HashMap, Entry};
use std::sync::Arc;
use extrinsic_store::{Data, Store as ExtrinsicStore};
use table::{self, Table, Context as TableContextTrait};
use polkadot_primitives::{Block, BlockId, Hash, SessionKey};
use polkadot_primitives::parachain::{Id as ParaId, Collation, Extrinsic, CandidateReceipt,
AttestedCandidate, ParachainHost, PoVBlock, ValidatorIndex,
};
use parking_lot::Mutex;
use futures::prelude::*;
use super::{GroupInfo, TableRouter};
use self::includable::IncludabilitySender;
use primitives::{ed25519, Pair};
use runtime_primitives::traits::ProvideRuntimeApi;
mod includable;
pub use self::includable::Includable;
pub use table::{SignedStatement, Statement};
pub use table::generic::Statement as GenericStatement;
struct TableContext {
parent_hash: Hash,
key: Arc,
groups: HashMap,
index_mapping: HashMap,
}
impl table::Context for TableContext {
fn is_member_of(&self, authority: ValidatorIndex, group: &ParaId) -> bool {
let key = match self.index_mapping.get(&authority) {
Some(val) => val,
None => return false,
};
self.groups.get(group).map_or(false, |g| g.validity_guarantors.get(&key).is_some())
}
fn requisite_votes(&self, group: &ParaId) -> usize {
self.groups.get(group).map_or(usize::max_value(), |g| g.needed_validity)
}
}
impl TableContext {
fn local_id(&self) -> SessionKey {
self.key.public().into()
}
fn local_index(&self) -> ValidatorIndex {
let id = self.local_id();
self
.index_mapping
.iter()
.find(|(_, k)| k == &&id)
.map(|(i, _)| *i)
.unwrap()
}
fn sign_statement(&self, statement: table::Statement) -> table::SignedStatement {
let signature = ::sign_table_statement(&statement, &self.key, &self.parent_hash).into();
table::SignedStatement {
statement,
signature,
sender: self.local_index(),
}
}
}
pub(crate) enum Validation {
Valid(PoVBlock, Extrinsic),
Invalid(PoVBlock), // should take proof.
}
enum ValidationWork {
Done(Validation),
InProgress,
Error(String),
}
#[cfg(test)]
impl ValidationWork {
fn is_in_progress(&self) -> bool {
match *self {
ValidationWork::InProgress => true,
_ => false,
}
}
fn is_done(&self) -> bool {
match *self {
ValidationWork::Done(_) => true,
_ => false,
}
}
}
// A shared table object.
struct SharedTableInner {
table: Table,
trackers: Vec,
extrinsic_store: ExtrinsicStore,
validated: HashMap,
}
impl SharedTableInner {
// Import a single statement. Provide a handle to a table router and a function
// used to determine if a referenced candidate is valid.
//
// the statement producer, if any, will produce only statements concerning the same candidate
// as the one just imported
fn import_remote_statement(
&mut self,
context: &TableContext,
router: &R,
statement: table::SignedStatement,
) -> Option::Future,
>> {
let summary = match self.table.import_statement(context, statement) {
Some(summary) => summary,
None => return None,
};
self.update_trackers(&summary.candidate, context);
let local_index = context.local_index();
let para_member = context.is_member_of(local_index, &summary.group_id);
let digest = &summary.candidate;
// TODO: consider a strategy based on the number of candidate votes as well.
// https://github.com/paritytech/polkadot/issues/218
let do_validation = para_member && match self.validated.entry(digest.clone()) {
Entry::Occupied(_) => false,
Entry::Vacant(entry) => {
entry.insert(ValidationWork::InProgress);
true
}
};
let work = if do_validation {
match self.table.get_candidate(&digest) {
None => {
let message = format!(
"Table inconsistency detected. Summary returned for candidate {} \
but receipt not present in table.",
digest,
);
warn!(target: "validation", "{}", message);
self.validated.insert(digest.clone(), ValidationWork::Error(message));
None
}
Some(candidate) => {
let fetch = router.fetch_pov_block(candidate).into_future();
Some(Work {
candidate_receipt: candidate.clone(),
fetch,
})
}
}
} else {
None
};
work.map(|work| ParachainWork {
extrinsic_store: self.extrinsic_store.clone(),
relay_parent: context.parent_hash.clone(),
work
})
}
fn update_trackers(&mut self, candidate: &Hash, context: &TableContext) {
let includable = self.table.candidate_includable(candidate, context);
for i in (0..self.trackers.len()).rev() {
if self.trackers[i].update_candidate(candidate.clone(), includable) {
self.trackers.swap_remove(i);
}
}
}
}
/// Produced after validating a candidate.
pub struct Validated {
/// A statement about the validity of the candidate.
statement: table::Statement,
/// The result of validation.
result: Validation,
}
impl Validated {
/// Note that we've validated a candidate with given hash and it is bad.
pub fn known_bad(hash: Hash, collation: PoVBlock) -> Self {
Validated {
statement: GenericStatement::Invalid(hash),
result: Validation::Invalid(collation),
}
}
/// Note that we've validated a candidate with given hash and it is good.
/// Extrinsic data required.
pub fn known_good(hash: Hash, collation: PoVBlock, extrinsic: Extrinsic) -> Self {
Validated {
statement: GenericStatement::Valid(hash),
result: Validation::Valid(collation, extrinsic),
}
}
/// Note that we've collated a candidate.
/// Extrinsic data required.
pub fn collated_local(
receipt: CandidateReceipt,
collation: PoVBlock,
extrinsic: Extrinsic,
) -> Self {
Validated {
statement: GenericStatement::Candidate(receipt),
result: Validation::Valid(collation, extrinsic),
}
}
/// Get a reference to the proof-of-validation block.
pub fn pov_block(&self) -> &PoVBlock {
match self.result {
Validation::Valid(ref b, _) | Validation::Invalid(ref b) => b,
}
}
/// Get a reference to the extrinsic data, if any.
pub fn extrinsic(&self) -> Option<&Extrinsic> {
match self.result {
Validation::Valid(_, ref ex) => Some(ex),
Validation::Invalid(_) => None,
}
}
}
/// Future that performs parachain validation work.
pub struct ParachainWork {
work: Work,
relay_parent: Hash,
extrinsic_store: ExtrinsicStore,
}
impl ParachainWork {
/// Prime the parachain work with an API reference for extracting
/// chain information.
pub fn prime(self, api: Arc
)
-> PrimedParachainWork<
Fetch,
impl Send + FnMut(&BlockId, &Collation) -> Result,
>
where
P: Send + Sync + 'static,
P::Api: ParachainHost,
{
let validate = move |id: &_, collation: &_| {
let res = ::collation::validate_collation(
&*api,
id,
collation,
);
match res {
Ok(e) => Ok(e),
Err(e) => {
debug!(target: "validation", "Encountered bad collation: {}", e);
Err(())
}
}
};
PrimedParachainWork { inner: self, validate }
}
/// Prime the parachain work with a custom validation function.
pub fn prime_with(self, validate: F) -> PrimedParachainWork
where F: FnMut(&BlockId, &Collation) -> Result
{
PrimedParachainWork { inner: self, validate }
}
}
struct Work {
candidate_receipt: CandidateReceipt,
fetch: Fetch
}
/// Primed statement producer.
pub struct PrimedParachainWork {
inner: ParachainWork,
validate: F,
}
impl Future for PrimedParachainWork
where
Fetch: Future,
F: FnMut(&BlockId, &Collation) -> Result,
Err: From<::std::io::Error>,
{
type Item = Validated;
type Error = Err;
fn poll(&mut self) -> Poll {
let work = &mut self.inner.work;
let candidate = &work.candidate_receipt;
let pov_block = try_ready!(work.fetch.poll());
let validation_res = (self.validate)(
&BlockId::hash(self.inner.relay_parent),
&Collation { pov: pov_block.clone(), receipt: candidate.clone() },
);
let candidate_hash = candidate.hash();
debug!(target: "validation", "Making validity statement about candidate {}: is_good? {:?}",
candidate_hash, validation_res.is_ok());
let (validity_statement, result) = match validation_res {
Err(()) => (
GenericStatement::Invalid(candidate_hash),
Validation::Invalid(pov_block),
),
Ok(extrinsic) => {
self.inner.extrinsic_store.make_available(Data {
relay_parent: self.inner.relay_parent,
parachain_id: work.candidate_receipt.parachain_index,
candidate_hash,
block_data: pov_block.block_data.clone(),
extrinsic: Some(extrinsic.clone()),
})?;
(
GenericStatement::Valid(candidate_hash),
Validation::Valid(pov_block, extrinsic)
)
}
};
Ok(Async::Ready(Validated {
statement: validity_statement,
result,
}))
}
}
/// A shared table object.
pub struct SharedTable {
context: Arc,
inner: Arc>,
}
impl Clone for SharedTable {
fn clone(&self) -> Self {
SharedTable {
context: self.context.clone(),
inner: self.inner.clone(),
}
}
}
impl SharedTable {
/// Create a new shared table.
///
/// Provide the key to sign with, and the parent hash of the relay chain
/// block being built.
pub fn new(
authorities: &[ed25519::Public],
groups: HashMap,
key: Arc,
parent_hash: Hash,
extrinsic_store: ExtrinsicStore,
) -> Self {
let index_mapping = authorities.iter().enumerate().map(|(i, k)| (i as ValidatorIndex, k.clone())).collect();
Self {
context: Arc::new(TableContext { groups, key, parent_hash, index_mapping, }),
inner: Arc::new(Mutex::new(SharedTableInner {
table: Table::default(),
validated: HashMap::new(),
trackers: Vec::new(),
extrinsic_store,
}))
}
}
/// Get the parent hash this table should hold statements localized to.
pub fn consensus_parent_hash(&self) -> &Hash {
&self.context.parent_hash
}
/// Get the local validator session key.
pub fn session_key(&self) -> SessionKey {
self.context.local_id()
}
/// Get group info.
pub fn group_info(&self) -> &HashMap {
&self.context.groups
}
/// Get extrinsic data for candidate with given hash, if any.
///
/// This will return `Some` for any candidates that have been validated
/// locally.
pub(crate) fn extrinsic_data(&self, hash: &Hash) -> Option {
self.inner.lock().validated.get(hash).and_then(|x| match *x {
ValidationWork::Error(_) => None,
ValidationWork::InProgress => None,
ValidationWork::Done(Validation::Invalid(_)) => None,
ValidationWork::Done(Validation::Valid(_, ref ex)) => Some(ex.clone()),
})
}
/// Import a single statement with remote source, whose signature has already been checked.
///
/// The statement producer, if any, will produce only statements concerning the same candidate
/// as the one just imported
pub fn import_remote_statement(
&self,
router: &R,
statement: table::SignedStatement,
) -> Option::Future,
>> {
self.inner.lock().import_remote_statement(&*self.context, router, statement)
}
/// Import many statements at once.
///
/// Provide an iterator yielding remote, pre-checked statements.
///
/// The statement producer, if any, will produce only statements concerning the same candidate
/// as the one just imported
pub fn import_remote_statements(&self, router: &R, iterable: I) -> U
where
R: TableRouter,
I: IntoIterator,
U: ::std::iter::FromIterator