Newer
Older
// 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 <http://www.gnu.org/licenses/>.
//! Propagation and agreement of candidates.
//!
//! Authorities are split into groups by parachain, and each authority might come
//! up its own candidate for their parachain. Within groups, authorities pass around
//! their candidates and produce statements of validity.
//!
//! Any candidate that receives majority approval by the authorities in a group
//! may be subject to inclusion, unless any authorities flag that candidate as invalid.
//!
//! Wrongly flagging as invalid should be strongly disincentivized, so that in the
//! equilibrium state it is not expected to happen. Likewise with the submission
//! of invalid blocks.
//!
//! Groups themselves may be compromised by malicious authorities.
use std::{
collections::{HashMap, HashSet},
pin::Pin,
sync::Arc,
time::{self, Duration, Instant},
task::{Poll, Context},
mem,
use availability_store::Store as AvailabilityStore;
use polkadot_primitives::{Hash, Block, BlockId, BlockNumber, Header};
use polkadot_primitives::parachain::{
Id as ParaId, Chain, DutyRoster, CandidateReceipt,
ParachainHost, AttestedCandidate, Statement as PrimitiveStatement, Message, OutgoingMessages,
Collation, PoVBlock, ErasureChunk, ValidatorSignature, ValidatorIndex,
ValidatorPair, ValidatorId,
use runtime_primitives::traits::{ProvideRuntimeApi, DigestFor};
use txpool_api::{TransactionPool, InPoolTransaction};
use futures::prelude::*;
use futures::{future::{self, Either, select, ready}, stream::unfold, task::{Spawn, SpawnExt}};
use collation::CollationFetch;
use dynamic_inclusion::DynamicInclusion;
use sp_timestamp::TimestampInherentData;
fn interval(duration: Duration) -> impl Stream<Item=()> + Send + Unpin {
unfold((), move |_| {
futures_timer::Delay::new(duration).map(|_| Some(((), ())))
}).map(drop)
}
asynchronous rob
committed
pub use self::collation::{
validate_collation, validate_incoming, message_queue_root, egress_roots, Collators,
asynchronous rob
committed
};
pub use self::shared_table::{
SharedTable, ParachainWork, PrimedParachainWork, Validated, Statement, SignedStatement,
pub use parachain::wasm_executor::{run_worker as run_validation_worker};
mod dynamic_inclusion;
mod evaluation;
pub mod collation;
// block size limit.
const MAX_TRANSACTIONS_SIZE: usize = 4 * 1024 * 1024;
/// Incoming messages; a series of sorted (ParaId, Message) pairs.
pub type Incoming = Vec<(ParaId, Vec<Message>)>;
/// A handle to a statement table router.
///
/// This is expected to be a lightweight, shared type like an `Arc`.
pub trait TableRouter: Clone {
/// Errors when fetching data from the network.
/// Future that resolves when candidate data is fetched.
type FetchValidationProof: Future<Output=Result<PoVBlock, Self::Error>>;
/// Call with local candidate data. This will make the data available on the network,
/// and sign, import, and broadcast a statement about the candidate.
fn local_collation(
&self,
collation: Collation,
receipt: CandidateReceipt,
outgoing: OutgoingMessages,
chunks: (ValidatorIndex, &[ErasureChunk])
);
asynchronous rob
committed
/// Fetch validation proof for a specific candidate.
fn fetch_pov_block(&self, candidate: &CandidateReceipt) -> Self::FetchValidationProof;
/// A long-lived network which can create parachain statement and BFT message routing processes on demand.
pub trait Network {
/// The error type of asynchronously building the table router.
type Error: std::fmt::Debug;
/// The table router type. This should handle importing of any statements,
/// routing statements to peers, and driving completion of any `StatementProducers`.
type TableRouter: TableRouter;
/// The future used for asynchronously building the table router.
/// This should not fail.
type BuildTableRouter: Future<Output=Result<Self::TableRouter,Self::Error>>;
/// Instantiate a table router using the given shared table.
/// Also pass through any outgoing messages to be broadcast to peers.
fn communication_for(
&self,
table: Arc<SharedTable>,
exit: exit_future::Exit,
) -> Self::BuildTableRouter;
/// Information about a specific group.
#[derive(Debug, Clone, Default)]
pub struct GroupInfo {
/// Authorities meant to check validity of candidates.
validity_guarantors: HashSet<ValidatorId>,
/// Number of votes needed for validity.
needed_validity: usize,
}
/// Sign a table statement against a parent hash.
/// The actual message signed is the encoded statement concatenated with the
/// parent hash.
pub fn sign_table_statement(statement: &Statement, key: &ValidatorPair, parent_hash: &Hash) -> ValidatorSignature {
// we sign using the primitive statement type because that's what the runtime
// expects. These types probably encode the same way so this clone could be optimized
// out in the future.
let mut encoded = PrimitiveStatement::from(statement.clone()).encode();
}
/// Check signature on table statement.
pub fn check_statement(
statement: &Statement,
signature: &ValidatorSignature,
signer: ValidatorId,
parent_hash: &Hash
) -> bool {
use runtime_primitives::traits::AppVerify;
let mut encoded = PrimitiveStatement::from(statement.clone()).encode();
signature.verify(&encoded[..], &signer)
}
/// Compute group info out of a duty roster and a local authority set.
pub fn make_group_info(
roster: DutyRoster,
authorities: &[ValidatorId],
local_id: Option<ValidatorId>,
) -> Result<(HashMap<ParaId, GroupInfo>, Option<LocalDuty>), Error> {
if roster.validator_duty.len() != authorities.len() {
return Err(Error::InvalidDutyRosterLength {
expected: authorities.len(),
got: roster.validator_duty.len()
});
let mut local_validation = None;
let mut map = HashMap::new();
let duty_iter = authorities.iter().zip(&roster.validator_duty);
for (i, (authority, v_duty)) in duty_iter.enumerate() {
if Some(authority) == local_id.as_ref() {
local_validation = Some(v_duty.clone());
match *v_duty {
Chain::Relay => {}, // does nothing for now.
Chain::Parachain(ref id) => {
map.entry(id.clone()).or_insert_with(GroupInfo::default)
.validity_guarantors
.insert(authority.clone());
}
}
}
for live_group in map.values_mut() {
let validity_len = live_group.validity_guarantors.len();
live_group.needed_validity = validity_len / 2 + validity_len % 2;
}
let local_duty = local_validation.map(|v| LocalDuty {
validation: v,
index: local_index as u32,
});
Ok((map, local_duty))
}
/// Compute the (target, root, messages) of all outgoing queues.
pub fn outgoing_queues(outgoing_targeted: &'_ OutgoingMessages)
-> impl Iterator<Item=(ParaId, Hash, Vec<Message>)> + '_
{
outgoing_targeted.message_queues().filter_map(|queue| {
let target = queue.get(0)?.target;
let queue_root = message_queue_root(queue);
let queue_data = queue.iter().map(|msg| msg.clone().into()).collect();
Some((target, queue_root, queue_data))
})
}
// finds the first key we are capable of signing with out of the given set of validators,
// if any.
fn signing_key(validators: &[ValidatorId], keystore: &KeyStorePtr) -> Option<Arc<ValidatorPair>> {
let keystore = keystore.read();
validators.iter()
.find_map(|v| {
keystore.key_pair::<ValidatorPair>(&v).ok()
})
.map(|pair| Arc::new(pair))
/// Constructs parachain-agreement instances.
struct ParachainValidation<C, N, P> {
/// The client instance.
/// The backing network handle.
/// handle to remote task executor
availability_store: AvailabilityStore,
/// Live agreements. Maps relay chain parent hashes to attestation
/// instances.
live_instances: Mutex<HashMap<Hash, Arc<AttestationTracker>>>,
impl<C, N, P> ParachainValidation<C, N, P> where
P: ProvideRuntimeApi + HeaderBackend<Block> + BlockBody<Block> + Send + Sync + 'static,
P::Api: ParachainHost<Block> + BlockBuilderApi<Block> + ApiExt<Block, Error = sp_blockchain::Error>,
/// Get an attestation table for given parent hash.
///
/// This starts a parachain agreement process on top of the parent hash if
///
/// Additionally, this will trigger broadcast of data to the new block's duty
/// roster.
)
-> Result<Arc<AttestationTracker>, Error>
{
let mut live_instances = self.live_instances.lock();
if let Some(tracker) = live_instances.get(&parent_hash) {
return Ok(tracker.clone());
}
let validators = self.client.runtime_api().validators(&id)?;
let sign_with = signing_key(&validators[..], keystore);
let duty_roster = self.client.runtime_api().duty_roster(&id)?;
let (group_info, local_duty) = make_group_info(
duty_roster,
&validators,
sign_with.as_ref().map(|k| k.public()),
info!(
"Starting parachain attestation session on top of parent {:?}. Local parachain duty is {:?}",
parent_hash,
asynchronous rob
committed
let active_parachains = self.client.runtime_api().active_parachains(&id)?;
debug!(target: "validation", "Active parachains: {:?}", active_parachains);
asynchronous rob
committed
// If we are a validator, we need to store our index in this round in availability store.
// This will tell which erasure chunk we should store.
if let Some(ref local_duty) = local_duty {
if let Err(e) = self.availability_store.add_validator_index_and_n_validators(
&parent_hash,
local_duty.index,
validators.len() as u32,
) {
warn!(
target: "validation",
"Failed to add validator index and n_validators to the availability-store: {:?}", e
)
}
}
let table = Arc::new(SharedTable::new(
let (_drop_signal, exit) = exit_future::signal();
let router = self.network.communication_for(
table.clone(),
);
if let Some((Chain::Parachain(id), index)) = local_duty.as_ref().map(|d| (d.validation, d.index)) {
self.launch_work(parent_hash, id, router, max_block_data_size, validators.len(), index, exit);
let tracker = Arc::new(AttestationTracker {
table,
});
live_instances.insert(parent_hash, tracker.clone());
Ok(tracker)
}
/// Retain validation sessions matching predicate.
fn retain<F: FnMut(&Hash) -> bool>(&self, mut pred: F) {
self.live_instances.lock().retain(|k, _| pred(k))
}
// launch parachain work asynchronously.
fn launch_work(
&self,
relay_parent: Hash,
validation_para: ParaId,
build_router: N::BuildTableRouter,
max_block_data_size: Option<u64>,
authorities_num: usize,
local_id: ValidatorIndex,
exit: exit_future::Exit,
) {
let (collators, client) = (self.collators.clone(), self.client.clone());
let availability_store = self.availability_store.clone();
let with_router = move |router: N::TableRouter| {
asynchronous rob
committed
// fetch a local collation from connected collators.
let collation_work = CollationFetch::new(
validation_para,
relay_parent,
collators,
asynchronous rob
committed
);
collation_work.map(move |result| match result {
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
Ok((collation, outgoing_targeted, fees_charged)) => {
match produce_receipt_and_chunks(
authorities_num,
&collation.pov,
&outgoing_targeted,
fees_charged,
&collation.info,
) {
Ok((receipt, chunks)) => {
// Apparently the `async move` block is the only way to convince
// the compiler that we are not moving values out of borrowed context.
let av_clone = availability_store.clone();
let chunks_clone = chunks.clone();
let receipt_clone = receipt.clone();
let res = async move {
if let Err(e) = av_clone.clone().add_erasure_chunks(
relay_parent.clone(),
receipt_clone,
chunks_clone,
).await {
warn!(target: "validation", "Failed to add erasure chunks: {}", e);
}
}
.unit_error()
.boxed()
.then(move |_| {
router.local_collation(collation, receipt, outgoing_targeted, (local_id, &chunks));
ready(())
});
Some(res)
}
Err(e) => {
warn!(target: "validation", "Failed to produce a receipt: {:?}", e);
None
asynchronous rob
committed
warn!(target: "validation", "Failed to collate candidate: {:?}", e);
let router = build_router
.map_ok(with_router)
.map_err(|e| {
warn!(target: "validation" , "Failed to build table router: {:?}", e);
if self.handle.spawn(cancellable_work).is_err() {
error!("Failed to spawn cancellable work task");
}
/// Parachain validation for a single block.
_drop_signal: exit_future::Signal,
}
/// Polkadot proposer factory.
pub struct ProposerFactory<C, N, P, SC, TxPool: TransactionPool> {
parachain_validation: Arc<ParachainValidation<C, N, P>>,
_select_chain: SC,
impl<C, N, P, SC, TxPool> ProposerFactory<C, N, P, SC, TxPool> where
C: Collators + Send + Sync + Unpin + 'static,
C::Collation: Send + Unpin + 'static,
P: ProvideRuntimeApi + HeaderBackend<Block> + Send + Sync + 'static,
P::Api: ParachainHost<Block> +
BlockBuilderApi<Block> +
BabeApi<Block> +
N: Network + Send + Sync + 'static,
N::TableRouter: Send + 'static,
{
/// Create a new proposer factory.
_select_chain: SC,
availability_store: AvailabilityStore,
let parachain_validation = Arc::new(ParachainValidation {
client: client.clone(),
network,
collators,
handle: thread_pool.clone(),
availability_store: availability_store.clone(),
live_instances: Mutex::new(HashMap::new()),
});
let service_handle = crate::attestation_service::start(
_select_chain.clone(),
parachain_validation.clone(),
parachain_validation,
impl<C, N, P, SC, TxPool> consensus::Environment<Block> for ProposerFactory<C, N, P, SC, TxPool> where
P: ProvideRuntimeApi + HeaderBackend<Block> + BlockBody<Block> + Send + Sync + 'static,
P::Api: ParachainHost<Block> +
BlockBuilderApi<Block> +
BabeApi<Block> +
parent_header: &Header,
) -> Result<Self::Proposer, Error> {
let parent_hash = parent_header.hash();
let parent_id = BlockId::hash(parent_hash);
let tracker = self.parachain_validation.get_or_instantiate(
client: self.parachain_validation.client.clone(),
tracker,
parent_hash,
parent_id,
parent_number: parent_header.number,
transaction_pool: self.transaction_pool.clone(),
slot_duration: self.babe_slot_duration,
}
/// The Polkadot proposer logic.
pub struct Proposer<C: Send + Sync, TxPool: TransactionPool> where
C: ProvideRuntimeApi + HeaderBackend<Block>,
{
parent_number: BlockNumber,
impl<C, TxPool> consensus::Proposer<Block> for Proposer<C, TxPool> where
TxPool: TransactionPool<Block=Block> + 'static,
C: ProvideRuntimeApi + HeaderBackend<Block> + Send + Sync + 'static,
C::Api: ParachainHost<Block> + BlockBuilderApi<Block> + ApiExt<Block, Error = sp_blockchain::Error>,
type Error = Error;
type Create = Either<CreateProposal<C, TxPool>, future::Ready<Result<Block, Error>>>;
inherent_digests: DigestFor<Block>,
max_duration: Duration,
) -> Self::Create {
const ATTEMPT_PROPOSE_EVERY: Duration = Duration::from_millis(100);
const SLOT_DURATION_DENOMINATOR: u64 = 3; // wait up to 1/3 of the slot for candidates.
let initial_included = self.tracker.table.includable_count();
let now = Instant::now();
let dynamic_inclusion = DynamicInclusion::new(
self.tracker.table.num_parachains(),
self.tracker.started,
Duration::from_millis(self.slot_duration / SLOT_DURATION_DENOMINATOR),
);
let enough_candidates = dynamic_inclusion.acceptable_in(
now,
).unwrap_or_else(|| Duration::from_millis(1));
let believed_timestamp = match inherent_data.timestamp_inherent_data() {
Ok(timestamp) => timestamp,
Err(e) => return Either::Right(future::err(Error::InherentError(e))),
// set up delay until next allowed timestamp.
let current_timestamp = current_timestamp();
let delay_future = if current_timestamp >= believed_timestamp {
Some(Delay::new(Duration::from_millis (current_timestamp - believed_timestamp)))
let timing = ProposalTiming {
attempt_propose: Box::new(interval(ATTEMPT_PROPOSE_EVERY)),
enough_candidates: Delay::new(enough_candidates),
last_included: initial_included,
let deadline_diff = max_duration - max_duration / 3;
let deadline = match Instant::now().checked_add(deadline_diff) {
None => return Either::Right(
future::err(Error::DeadlineComputeFailure(deadline_diff)),
),
Some(d) => d,
};
state: CreateProposalState::Pending(CreateProposalData {
parent_hash: self.parent_hash.clone(),
parent_number: self.parent_number.clone(),
parent_id: self.parent_id.clone(),
client: self.client.clone(),
transaction_pool: self.transaction_pool.clone(),
table: self.tracker.table.clone(),
believed_minimum_timestamp: believed_timestamp,
timing,
inherent_data: Some(inherent_data),
inherent_digests,
// leave some time for the proposal finalisation
deadline,
})
time::SystemTime::now().duration_since(time::UNIX_EPOCH)
.expect("now always later than unix epoch; qed")
dynamic_inclusion: DynamicInclusion,
enough_candidates: Delay,
last_included: usize,
}
impl ProposalTiming {
// whether it's time to attempt a proposal.
// shouldn't be called outside of the context of a task.
fn poll(&mut self, cx: &mut Context, included: usize) -> Poll<()> {
// first drain from the interval so when the minimum delay is up
// we don't have any notifications built up.
//
// this interval is just meant to produce periodic task wakeups
// that lead to the `dynamic_inclusion` getting updated as necessary.
while let Poll::Ready(x) = self.attempt_propose.poll_next_unpin(cx) {
x.expect("timer still alive; intervals never end; qed");
}
// wait until the minimum time has passed.
if let Some(mut minimum) = self.minimum.take() {
if let Poll::Pending = minimum.poll_unpin(cx) {
if included == self.last_included {
return self.enough_candidates.poll_unpin(cx);
}
// the amount of includable candidates has changed. schedule a wakeup
// if it's not sufficient anymore.
match self.dynamic_inclusion.acceptable_in(Instant::now(), included) {
Some(instant) => {
self.last_included = included;
self.enough_candidates.reset(Instant::now() + instant);
self.enough_candidates.poll_unpin(cx)
/// Future which resolves upon the creation of a proposal.
pub struct CreateProposal<C: Send + Sync, TxPool> {
state: CreateProposalState<C, TxPool>,
}
/// Current status of the proposal future.
/// Pending inclusion, with given proposal data.
/// Represents the state when we switch from pending to fired.
Switching,
/// Block proposing has fired.
Fired(tokio_executor::blocking::Blocking<Result<Block, Error>>),
}
/// Inner data of the create proposal.
struct CreateProposalData<C: Send + Sync, TxPool> {
parent_number: BlockNumber,
table: Arc<SharedTable>,
timing: ProposalTiming,
inherent_data: Option<InherentData>,
inherent_digests: DigestFor<Block>,
impl<C, TxPool> CreateProposalData<C, TxPool> where
TxPool: TransactionPool<Block=Block>,
C: ProvideRuntimeApi + HeaderBackend<Block> + Send + Sync,
C::Api: ParachainHost<Block> + BlockBuilderApi<Block> + ApiExt<Block, Error = sp_blockchain::Error>,
fn propose_with(mut self, candidates: Vec<AttestedCandidate>) -> Result<Block, Error> {
use runtime_primitives::traits::{Hash as HashT, BlakeTwo256};
const MAX_TRANSACTIONS: usize = 40;
let mut inherent_data = self.inherent_data
.take()
.expect("CreateProposal is not polled after finishing; qed");
inherent_data.put_data(polkadot_runtime::NEW_HEADS_IDENTIFIER, &candidates)
.map_err(Error::InherentError)?;
let runtime_api = self.client.runtime_api();
let mut block_builder = BlockBuilder::new(
&*self.client,
self.client.expect_block_hash_from_id(&self.parent_id)?,
self.client.expect_block_number_from_id(&self.parent_id)?,
false,
self.inherent_digests.clone(),
)?;
let inherents = runtime_api.inherent_extrinsics(&self.parent_id, inherent_data)?;
for inherent in inherents {
block_builder.push(inherent)?;
}
let mut unqueue_invalid = Vec::new();
let mut pending_size = 0;
let ready_iter = self.transaction_pool.ready();
for ready in ready_iter.take(MAX_TRANSACTIONS) {
if pending_size + encoded_size >= MAX_TRANSACTIONS_SIZE {
debug!("Consensus deadline reached when pushing block transactions, proceeding with proposing.");
debug!("[{:?}] Pushed to the block.", ready.hash());
pending_size += encoded_size;
Err(sp_blockchain::Error::ApplyExtrinsicFailed(sp_blockchain::ApplyExtrinsicFailed::Validity(e)))
if e.exhausted_resources() =>
{
debug!("Block is full, proceed with proposing.");
break;
}
Err(e) => {
trace!(target: "transaction-pool", "Invalid transaction: {}", e);
self.transaction_pool.remove_invalid(&unqueue_invalid);
info!("Prepared block for proposing at {} [hash: {:?}; parent_hash: {}; extrinsics: [{}]]",
new_block.header.number,
Hash::from(new_block.header.hash()),
new_block.header.parent_hash,
new_block.extrinsics.iter()
.map(|xt| format!("{}", BlakeTwo256::hash_of(xt)))
.collect::<Vec<_>>()
.join(", ")
);
// TODO: full re-evaluation (https://github.com/paritytech/polkadot/issues/216)
let active_parachains = runtime_api.active_parachains(&self.parent_id)?;
assert!(evaluation::evaluate_initial(
&self.parent_hash,
self.parent_number,
C: ProvideRuntimeApi + HeaderBackend<Block> + Send + Sync + 'static,
C::Api: ParachainHost<Block> + BlockBuilderApi<Block> + ApiExt<Block, Error = sp_blockchain::Error>,
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let mut state = CreateProposalState::Switching;
mem::swap(&mut state, &mut self.state);
// 1. try to propose if we have enough includable candidates and other
let data = match state {
CreateProposalState::Pending(mut data) => {
let included = data.table.includable_count();
match data.timing.poll(cx, included) {
Poll::Pending => {
self.state = CreateProposalState::Pending(data);
return Poll::Pending
},
Poll::Ready(()) => (),
}
data
},
CreateProposalState::Switching =>
unreachable!(
"State Switching are only created on call, \
and immediately swapped out; \
the data being read is from state; \
thus Switching will never be reachable here; qed"
),
CreateProposalState::Fired(mut future) => {
let ret = Pin::new(&mut future).poll(cx);
self.state = CreateProposalState::Fired(future);
return ret
},
};
// 2. propose
let mut future = tokio_executor::blocking::run(move || {
let proposed_candidates = data.table.proposed_set();
data.propose_with(proposed_candidates)
});
let polled = Pin::new(&mut future).poll(cx);
self.state = CreateProposalState::Fired(future);
#[cfg(test)]
mod tests {
use super::*;
use sp_keyring::Sr25519Keyring;
#[test]
fn sign_and_check_statement() {
let statement: Statement = GenericStatement::Valid([1; 32].into());
let parent_hash = [2; 32].into();
let sig = sign_table_statement(&statement, &Sr25519Keyring::Alice.pair().into(), &parent_hash);
assert!(check_statement(&statement, &sig, Sr25519Keyring::Alice.public().into(), &parent_hash));
assert!(!check_statement(&statement, &sig, Sr25519Keyring::Alice.public().into(), &[0xff; 32].into()));
assert!(!check_statement(&statement, &sig, Sr25519Keyring::Bob.public().into(), &parent_hash));