// Copyright 2018-2020 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 . use std::collections::{HashMap, HashSet}; use std::io; use std::sync::Arc; use std::thread; use log::{error, info, trace, warn}; use sp_blockchain::{Result as ClientResult}; use sp_runtime::traits::{Header as HeaderT, Block as BlockT, HashFor, BlakeTwo256}; use sp_api::{ApiExt, ProvideRuntimeApi}; use client::{ BlockchainEvents, BlockBackend, blockchain::ProvideCache, }; use consensus_common::{ self, BlockImport, BlockCheckParams, BlockImportParams, Error as ConsensusError, ImportResult, import_queue::CacheKeyId, }; use polkadot_primitives::{Block, BlockId, Hash}; use polkadot_primitives::parachain::{ ParachainHost, ValidatorId, AbridgedCandidateReceipt, AvailableData, ValidatorPair, ErasureChunk, }; use futures::{prelude::*, future::select, channel::{mpsc, oneshot}, task::{Spawn, SpawnExt}}; use futures::future::AbortHandle; use keystore::KeyStorePtr; use tokio::runtime::{Handle, Runtime as LocalRuntime}; use crate::{LOG_TARGET, ErasureNetworking}; use crate::store::Store; /// Errors that may occur. #[derive(Debug, derive_more::Display, derive_more::From)] pub(crate) enum Error { #[from] StoreError(io::Error), #[display(fmt = "Validator's id and number of validators at block with parent {} not found", relay_parent)] IdAndNValidatorsNotFound { relay_parent: Hash }, } /// Used in testing to interact with the worker thread. #[cfg(test)] pub(crate) struct WithWorker(Box); #[cfg(test)] impl std::fmt::Debug for WithWorker { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "") } } /// Messages sent to the `Worker`. /// /// Messages are sent in a number of different scenarios, /// for instance, when: /// * importing blocks in `BlockImport` implementation, /// * recieving finality notifications, /// * when the `Store` api is used by outside code. #[derive(Debug)] pub(crate) enum WorkerMsg { IncludedParachainBlocks(IncludedParachainBlocks), Chunks(Chunks), CandidatesFinalized(CandidatesFinalized), MakeAvailable(MakeAvailable), #[cfg(test)] WithWorker(WithWorker), } /// A notification of a parachain block included in the relay chain. #[derive(Debug)] pub(crate) struct IncludedParachainBlock { /// The abridged candidate receipt, extracted from a relay-chain block. pub candidate: AbridgedCandidateReceipt, /// The data to keep available from the candidate, if known. pub available_data: Option, } /// The receipts of the heads included into the block with a given parent. #[derive(Debug)] pub(crate) struct IncludedParachainBlocks { /// The blocks themselves. pub blocks: Vec, /// A sender to signal the result asynchronously. pub result: oneshot::Sender>, } /// We have received chunks we requested. #[derive(Debug)] pub(crate) struct Chunks { /// The hash of the parachain candidate these chunks belong to. pub candidate_hash: Hash, /// The chunks pub chunks: Vec, /// The number of validators present at the candidate's relay-parent. pub n_validators: u32, /// A sender to signal the result asynchronously. pub result: oneshot::Sender>, } /// These candidates have been finalized, so unneded availability may be now pruned #[derive(Debug)] pub(crate) struct CandidatesFinalized { /// The relay parent of the block that was finalized. relay_parent: Hash, /// The hashes of candidates that were finalized in this block. included_candidates: HashSet, } /// The message that corresponds to `make_available` call of the crate API. #[derive(Debug)] pub(crate) struct MakeAvailable { /// The hash of the candidate for which we are publishing data. pub candidate_hash: Hash, /// The data to make available. pub available_data: AvailableData, /// A sender to signal the result asynchronously. pub result: oneshot::Sender>, } /// Description of a chunk we are listening for. #[derive(Hash, Debug, PartialEq, Eq)] struct ListeningKey { candidate_hash: Hash, index: u32, } /// An availability worker with it's inner state. pub(super) struct Worker { availability_store: Store, listening_for: HashMap, sender: mpsc::UnboundedSender, } /// The handle to the `Worker`. pub(super) struct WorkerHandle { thread: Option>>, sender: mpsc::UnboundedSender, exit_signal: Option, } impl WorkerHandle { pub(crate) fn to_worker(&self) -> &mpsc::UnboundedSender { &self.sender } } impl Drop for WorkerHandle { fn drop(&mut self) { if let Some(signal) = self.exit_signal.take() { let _ = signal.fire(); } if let Some(thread) = self.thread.take() { if let Err(_) = thread.join() { error!(target: LOG_TARGET, "Errored stopping the thread"); } } } } fn fetch_candidates

(client: &P, extrinsics: Vec<::Extrinsic>, parent: &BlockId) -> ClientResult>> where P: ProvideRuntimeApi, P::Api: ParachainHost, // Rust bug: https://github.com/rust-lang/rust/issues/24159 sp_api::StateBackendFor: sp_api::StateBackend>, { let api = client.runtime_api(); let candidates = if api.has_api_with::, _>( parent, |version| version >= 2, ).map_err(|e| ConsensusError::ChainLookup(e.to_string()))? { api.get_heads(&parent, extrinsics) .map_err(|e| ConsensusError::ChainLookup(e.to_string()))? } else { None }; Ok(candidates) } /// Creates a task to prune entries in availability store upon block finalization. async fn prune_unneeded_availability(client: Arc

, mut sender: S) where P: ProvideRuntimeApi + BlockchainEvents + BlockBackend + Send + Sync + 'static, P::Api: ParachainHost + ApiExt, S: Sink + Clone + Send + Sync + Unpin, // Rust bug: https://github.com/rust-lang/rust/issues/24159 sp_api::StateBackendFor: sp_api::StateBackend>, { let mut finality_notification_stream = client.finality_notification_stream(); while let Some(notification) = finality_notification_stream.next().await { let hash = notification.hash; let parent_hash = notification.header.parent_hash; let extrinsics = match client.block_body(&BlockId::hash(hash)) { Ok(Some(extrinsics)) => extrinsics, Ok(None) => { error!( target: LOG_TARGET, "No block body found for imported block {:?}", hash, ); continue; } Err(e) => { error!( target: LOG_TARGET, "Failed to get block body for imported block {:?}: {:?}", hash, e, ); continue; } }; let included_candidates = match fetch_candidates( &*client, extrinsics, &BlockId::hash(parent_hash), ) { Ok(Some(candidates)) => candidates .into_iter() .map(|c| c.hash()) .collect(), Ok(None) => { warn!( target: LOG_TARGET, "Failed to extract candidates from block body of imported block {:?}", hash ); continue; } Err(e) => { warn!( target: LOG_TARGET, "Failed to fetch block body for imported block {:?}: {:?}", hash, e ); continue; } }; let msg = WorkerMsg::CandidatesFinalized(CandidatesFinalized { relay_parent: parent_hash, included_candidates }); if let Err(_) = sender.send(msg).await { break; } } } impl Worker { // Called on startup of the worker to initiate fetch from network for all awaited chunks. fn initiate_all_fetches( &mut self, runtime_handle: &Handle, erasure_network: &EN, sender: &mut mpsc::UnboundedSender, ) { if let Some(awaited_chunks) = self.availability_store.awaited_chunks() { for awaited_chunk in awaited_chunks { if let Err(e) = self.initiate_fetch( runtime_handle, erasure_network, sender, awaited_chunk.relay_parent, awaited_chunk.candidate_hash, ) { warn!(target: LOG_TARGET, "Failed to register network listener: {}", e); } } } } // initiates a fetch from network for the described chunk, with our local index. fn initiate_fetch( &mut self, runtime_handle: &Handle, erasure_network: &EN, sender: &mut mpsc::UnboundedSender, relay_parent: Hash, candidate_hash: Hash, ) -> Result<(), Error> { let (local_id, n_validators) = self.availability_store .get_validator_index_and_n_validators(&relay_parent) .ok_or(Error::IdAndNValidatorsNotFound { relay_parent })?; // fast exit for if we already have the chunk. if self.availability_store.get_erasure_chunk(&candidate_hash, local_id as _).is_some() { return Ok(()) } trace!( target: LOG_TARGET, "Initiating fetch for erasure-chunk at parent {} with candidate-hash {}", relay_parent, candidate_hash, ); let fut = erasure_network.fetch_erasure_chunk(&candidate_hash, local_id); let mut sender = sender.clone(); let (fut, signal) = future::abortable(async move { let chunk = match fut.await { Ok(chunk) => chunk, Err(e) => { warn!(target: LOG_TARGET, "Unable to fetch erasure-chunk from network: {:?}", e); return } }; let (s, _) = oneshot::channel(); let _ = sender.send(WorkerMsg::Chunks(Chunks { candidate_hash, chunks: vec![chunk], n_validators, result: s, })).await; }.map(drop).boxed()); let key = ListeningKey { candidate_hash, index: local_id, }; self.listening_for.insert(key, signal); let _ = runtime_handle.spawn(fut); Ok(()) } fn on_parachain_blocks_received( &mut self, runtime_handle: &Handle, erasure_network: &EN, sender: &mut mpsc::UnboundedSender, blocks: Vec, ) -> Result<(), Error> { // First we have to add the receipts themselves. for IncludedParachainBlock { candidate, available_data } in blocks.into_iter() { let _ = self.availability_store.add_candidate(&candidate); if let Some(_available_data) = available_data { // Should we be breaking block into chunks here and gossiping it and so on? } // This leans on the codebase-wide assumption that the `relay_parent` // of all candidates in a block matches the parent hash of that block. // // In the future this will not always be true. let candidate_hash = candidate.hash(); let _ = self.availability_store.note_candidates_with_relay_parent( &candidate.relay_parent, &[candidate_hash], ); if let Err(e) = self.initiate_fetch( runtime_handle, erasure_network, sender, candidate.relay_parent, candidate_hash, ) { warn!(target: LOG_TARGET, "Failed to register chunk listener: {}", e); } } Ok(()) } // Handles chunks that were required. fn on_chunks( &mut self, candidate_hash: Hash, chunks: Vec, n_validators: u32, ) -> Result<(), Error> { for c in &chunks { let key = ListeningKey { candidate_hash, index: c.index, }; // remove bookkeeping so network does not attempt to fetch // any longer. if let Some(exit_signal) = self.listening_for.remove(&key) { exit_signal.abort(); } } self.availability_store.add_erasure_chunks( n_validators, &candidate_hash, chunks, )?; Ok(()) } /// Starts a worker with a given availability store and a gossip messages provider. pub fn start( availability_store: Store, erasure_network: EN, ) -> WorkerHandle { let (sender, mut receiver) = mpsc::unbounded(); let mut worker = Worker { availability_store, listening_for: HashMap::new(), sender: sender.clone(), }; let sender = sender.clone(); let (signal, exit) = exit_future::signal(); let handle = thread::spawn(move || -> io::Result<()> { let mut runtime = LocalRuntime::new()?; let mut sender = worker.sender.clone(); let runtime_handle = runtime.handle().clone(); // On startup, initiates fetch from network for all // entries in the awaited frontier. worker.initiate_all_fetches(runtime.handle(), &erasure_network, &mut sender); let process_notification = async move { while let Some(msg) = receiver.next().await { trace!(target: LOG_TARGET, "Received message {:?}", msg); let res = match msg { WorkerMsg::IncludedParachainBlocks(msg) => { let IncludedParachainBlocks { blocks, result, } = msg; let res = worker.on_parachain_blocks_received( &runtime_handle, &erasure_network, &mut sender, blocks, ); let _ = result.send(res); Ok(()) } WorkerMsg::Chunks(msg) => { let Chunks { candidate_hash, chunks, n_validators, result, } = msg; let res = worker.on_chunks( candidate_hash, chunks, n_validators, ); let _ = result.send(res); Ok(()) } WorkerMsg::CandidatesFinalized(msg) => { let CandidatesFinalized { relay_parent, included_candidates } = msg; worker.availability_store.candidates_finalized( relay_parent, included_candidates, ) } WorkerMsg::MakeAvailable(msg) => { let MakeAvailable { candidate_hash, available_data, result } = msg; let res = worker.availability_store .make_available(candidate_hash, available_data) .map_err(|e| e.into()); let _ = result.send(res); Ok(()) } #[cfg(test)] WorkerMsg::WithWorker(with_worker) => { (with_worker.0)(&mut worker); Ok(()) } }; if let Err(_) = res { warn!(target: LOG_TARGET, "An error occured while processing a message"); } } }; runtime.spawn(select(process_notification.boxed(), exit.clone()).map(drop)); runtime.block_on(exit); info!(target: LOG_TARGET, "Availability worker exiting"); Ok(()) }); WorkerHandle { thread: Some(handle), sender, exit_signal: Some(signal), } } } /// Implementor of the [`BlockImport`] trait. /// /// Used to embed `availability-store` logic into the block imporing pipeline. /// /// [`BlockImport`]: https://substrate.dev/rustdocs/v1.0/substrate_consensus_common/trait.BlockImport.html pub struct AvailabilityBlockImport { inner: I, client: Arc

, keystore: KeyStorePtr, to_worker: mpsc::UnboundedSender, exit_signal: AbortHandle, } impl Drop for AvailabilityBlockImport { fn drop(&mut self) { self.exit_signal.abort(); } } impl BlockImport for AvailabilityBlockImport where I: BlockImport> + Send + Sync, I::Error: Into, P: ProvideRuntimeApi + ProvideCache, P::Api: ParachainHost, // Rust bug: https://github.com/rust-lang/rust/issues/24159 sp_api::StateBackendFor: sp_api::StateBackend { type Error = ConsensusError; type Transaction = sp_api::TransactionFor; fn import_block( &mut self, block: BlockImportParams, new_cache: HashMap>, ) -> Result { trace!( target: LOG_TARGET, "Importing block #{}, ({})", block.header.number(), block.post_hash(), ); if let Some(ref extrinsics) = block.body { let parent_id = BlockId::hash(*block.header.parent_hash()); // Extract our local position i from the validator set of the parent. let validators = self.client.runtime_api().validators(&parent_id) .map_err(|e| ConsensusError::ChainLookup(e.to_string()))?; let our_id = self.our_id(&validators); // Use a runtime API to extract all included erasure-roots from the imported block. let candidates = fetch_candidates(&*self.client, extrinsics.clone(), &parent_id) .map_err(|e| ConsensusError::ChainLookup(e.to_string()))?; match candidates { Some(candidates) => { match our_id { Some(our_id) => { trace!( target: LOG_TARGET, "Our validator id is {}, the candidates included are {:?}", our_id, candidates, ); let (s, _) = oneshot::channel(); // Inform the worker about the included parachain blocks. let blocks = candidates .into_iter() .map(|c| IncludedParachainBlock { candidate: c, available_data: None, }) .collect(); let msg = WorkerMsg::IncludedParachainBlocks(IncludedParachainBlocks { blocks, result: s, }); let _ = self.to_worker.unbounded_send(msg); } None => (), } } None => { trace!( target: LOG_TARGET, "No parachain heads were included in block {}", block.header.hash() ); }, } } self.inner.import_block(block, new_cache).map_err(Into::into) } fn check_block( &mut self, block: BlockCheckParams, ) -> Result { self.inner.check_block(block).map_err(Into::into) } } impl AvailabilityBlockImport { pub(crate) fn new( client: Arc

, block_import: I, spawner: impl Spawn, keystore: KeyStorePtr, to_worker: mpsc::UnboundedSender, ) -> Self where P: ProvideRuntimeApi + BlockBackend + BlockchainEvents + Send + Sync + 'static, P::Api: ParachainHost, P::Api: ApiExt, // Rust bug: https://github.com/rust-lang/rust/issues/24159 sp_api::StateBackendFor: sp_api::StateBackend>, { // This is not the right place to spawn the finality future, // it would be more appropriate to spawn it in the `start` method of the `Worker`. // However, this would make the type of the `Worker` and the `Store` itself // dependent on the types of client and executor, which would prove // not not so handy in the testing code. let (prune_available, exit_signal) = future::abortable(prune_unneeded_availability( client.clone(), to_worker.clone(), )); if let Err(_) = spawner.spawn(prune_available.map(drop)) { error!(target: LOG_TARGET, "Failed to spawn availability pruning task"); } AvailabilityBlockImport { client, inner: block_import, to_worker, keystore, exit_signal, } } fn our_id(&self, validators: &[ValidatorId]) -> Option { let keystore = self.keystore.read(); validators .iter() .enumerate() .find_map(|(i, v)| { keystore.key_pair::(&v).map(|_| i as u32).ok() }) } } #[cfg(test)] mod tests { use super::*; use futures::channel::oneshot; use std::sync::Arc; use std::pin::Pin; use tokio::runtime::Runtime; use parking_lot::Mutex; use crate::store::AwaitedFrontierEntry; #[derive(Default, Clone)] struct TestErasureNetwork { chunk_receivers: Arc >>>, } impl TestErasureNetwork { // adds a receiver. this returns a sender for the erasure-chunk // along with an exit future that fires when the erasure chunk has // been fully-processed fn add_receiver(&self, candidate_hash: Hash, index: u32) -> oneshot::Sender { let (sender, receiver) = oneshot::channel(); self.chunk_receivers.lock().insert((candidate_hash, index), receiver); sender } } impl ErasureNetworking for TestErasureNetwork { type Error = String; fn fetch_erasure_chunk(&self, candidate_hash: &Hash, index: u32) -> Pin> + Send>> { match self.chunk_receivers.lock().remove(&(*candidate_hash, index)) { Some(receiver) => receiver.then(|x| match x { Ok(x) => future::ready(Ok(x)).left_future(), Err(_) => future::pending().right_future(), }).boxed(), None => future::pending().boxed(), } } fn distribute_erasure_chunk( &self, _candidate_hash: Hash, _chunk: ErasureChunk ) {} } // This test tests that as soon as the worker receives info about new parachain blocks // included it registers gossip listeners for it's own chunks. Upon receiving the awaited // chunk messages the corresponding listeners are deregistered and these chunks are removed // from the awaited chunks set. #[test] fn receiving_gossip_chunk_removes_from_frontier() { let mut runtime = Runtime::new().unwrap(); let relay_parent = [1; 32].into(); let local_id = 2; let n_validators = 4; let store = Store::new_in_memory(); let mut candidate = AbridgedCandidateReceipt::default(); candidate.relay_parent = relay_parent; let candidate_hash = candidate.hash(); // Tell the store our validator's position and the number of validators at given point. store.note_validator_index_and_n_validators(&relay_parent, local_id, n_validators).unwrap(); let network = TestErasureNetwork::default(); let chunk_sender = network.add_receiver(candidate_hash, local_id); // At this point we shouldn't be waiting for any chunks. assert!(store.awaited_chunks().is_none()); let (s, r) = oneshot::channel(); let msg = WorkerMsg::IncludedParachainBlocks(IncludedParachainBlocks { blocks: vec![IncludedParachainBlock { candidate, available_data: None, }], result: s, }); let handle = Worker::start(store.clone(), network); // Tell the worker that the new blocks have been included into the relay chain. // This should trigger the registration of gossip message listeners for the // chunk topics. handle.sender.unbounded_send(msg).unwrap(); runtime.block_on(r).unwrap().unwrap(); // Make sure that at this point we are waiting for the appropriate chunk. assert_eq!( store.awaited_chunks().unwrap(), vec![AwaitedFrontierEntry { relay_parent, candidate_hash, validator_index: local_id, }].into_iter().collect() ); // Complete the chunk request. chunk_sender.send(ErasureChunk { chunk: vec![1, 2, 3], index: local_id as u32, proof: vec![], }).unwrap(); // wait until worker thread has de-registered the listener for a // particular chunk. loop { let (s, r) = oneshot::channel(); handle.sender.unbounded_send(WorkerMsg::WithWorker(WithWorker(Box::new(move |worker| { let key = ListeningKey { candidate_hash, index: local_id, }; let is_waiting = worker.listening_for.contains_key(&key); s.send(!is_waiting).unwrap(); // tell the test thread `true` if we are not waiting. })))).unwrap(); if runtime.block_on(r).unwrap() { break } } // The awaited chunk has been received so at this point we no longer wait for any chunks. assert_eq!(store.awaited_chunks().unwrap().len(), 0); } #[test] fn included_parachain_blocks_registers_listener() { let mut runtime = Runtime::new().unwrap(); let relay_parent = [1; 32].into(); let erasure_root_1 = [2; 32].into(); let erasure_root_2 = [3; 32].into(); let pov_block_hash_1 = [4; 32].into(); let pov_block_hash_2 = [5; 32].into(); let local_id = 2; let n_validators = 4; let mut candidate_1 = AbridgedCandidateReceipt::default(); candidate_1.commitments.erasure_root = erasure_root_1; candidate_1.pov_block_hash = pov_block_hash_1; candidate_1.relay_parent = relay_parent; let candidate_1_hash = candidate_1.hash(); let mut candidate_2 = AbridgedCandidateReceipt::default(); candidate_2.commitments.erasure_root = erasure_root_2; candidate_2.pov_block_hash = pov_block_hash_2; candidate_2.relay_parent = relay_parent; let candidate_2_hash = candidate_2.hash(); let store = Store::new_in_memory(); // Tell the store our validator's position and the number of validators at given point. store.note_validator_index_and_n_validators(&relay_parent, local_id, n_validators).unwrap(); // Let the store know about the candidates store.add_candidate(&candidate_1).unwrap(); store.add_candidate(&candidate_2).unwrap(); // And let the store know about the chunk from the second candidate. store.add_erasure_chunks( n_validators, &candidate_2_hash, vec![ErasureChunk { chunk: vec![1, 2, 3], index: local_id, proof: Vec::default(), }], ).unwrap(); let network = TestErasureNetwork::default(); let _ = network.add_receiver(candidate_1_hash, local_id); let _ = network.add_receiver(candidate_2_hash, local_id); let handle = Worker::start(store.clone(), network.clone()); { let (s, r) = oneshot::channel(); // Tell the worker to listen for chunks from candidate 2 (we alredy have a chunk from it). let listen_msg_2 = WorkerMsg::IncludedParachainBlocks(IncludedParachainBlocks { blocks: vec![IncludedParachainBlock { candidate: candidate_2, available_data: None, }], result: s, }); handle.sender.unbounded_send(listen_msg_2).unwrap(); runtime.block_on(r).unwrap().unwrap(); // The receiver for this chunk left intact => listener not registered. assert!(network.chunk_receivers.lock().contains_key(&(candidate_2_hash, local_id))); // more directly: let (s, r) = oneshot::channel(); handle.sender.unbounded_send(WorkerMsg::WithWorker(WithWorker(Box::new(move |worker| { let key = ListeningKey { candidate_hash: candidate_2_hash, index: local_id, }; let _ = s.send(worker.listening_for.contains_key(&key)); })))).unwrap(); assert!(!runtime.block_on(r).unwrap()); } { let (s, r) = oneshot::channel(); // Tell the worker to listen for chunks from candidate 1. // (we don't have a chunk from it yet). let listen_msg_1 = WorkerMsg::IncludedParachainBlocks(IncludedParachainBlocks { blocks: vec![IncludedParachainBlock { candidate: candidate_1, available_data: None, }], result: s, }); handle.sender.unbounded_send(listen_msg_1).unwrap(); runtime.block_on(r).unwrap().unwrap(); // The receiver taken => listener registered. assert!(!network.chunk_receivers.lock().contains_key(&(candidate_1_hash, local_id))); // more directly: let (s, r) = oneshot::channel(); handle.sender.unbounded_send(WorkerMsg::WithWorker(WithWorker(Box::new(move |worker| { let key = ListeningKey { candidate_hash: candidate_1_hash, index: local_id, }; let _ = s.send(worker.listening_for.contains_key(&key)); })))).unwrap(); assert!(runtime.block_on(r).unwrap()); } } }