rewrite network code to use notifications_protocol APIs from Substrate (#788)

* extract all network code to legacy submodule

* update references to legacy proto

* skeleton of futures-based protocol

* refactor skeleton to use background task

* rename communication_for to build_table_router

* implement internal message types for validation network

* basic ParachainNetwork and TableRouter implementations

* add some module docs

* remove exit-future from validation

* hack: adapt legacy protocol to lack of exit-future

* generalize RegisteredMessageValidator somewhat

* instantiate and teardown table routers

* clean up RouterInner drop logic

* implement most of the statement import loop

* implement statement loop in async/await

* remove unneeded TODO

* most of the collation skeleton

* send session keys and validator roles

* also send role after status

* use config in startup

* point TODO to issue

* fix test compilation
This commit is contained in:
Robert Habermeier
2020-02-10 15:20:45 +01:00
committed by GitHub
parent 6051a2b272
commit 9b23f3f1f0
21 changed files with 1941 additions and 867 deletions
+3
View File
@@ -3809,8 +3809,11 @@ name = "polkadot-network"
version = "0.7.20"
dependencies = [
"arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
"exit-future 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-scale-codec 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+4 -4
View File
@@ -65,10 +65,10 @@ use polkadot_cli::{
ProvideRuntimeApi, AbstractService, ParachainHost, IsKusama,
service::{self, Roles, SelectChain}
};
use polkadot_network::validation::{LeafWorkParams, ValidationNetwork};
use polkadot_network::legacy::validation::{LeafWorkParams, ValidationNetwork};
pub use polkadot_cli::{VersionInfo, load_spec, service::Configuration};
pub use polkadot_network::validation::Incoming;
pub use polkadot_network::legacy::validation::Incoming;
pub use polkadot_validation::SignedStatement;
pub use polkadot_primitives::parachain::CollatorId;
pub use sc_network::PeerId;
@@ -316,7 +316,7 @@ fn run_collator_node<S, P, Extrinsic>(
let is_known = move |block_hash: &Hash| {
use consensus_common::BlockStatus;
use polkadot_network::gossip::Known;
use polkadot_network::legacy::gossip::Known;
match known_oracle.block_status(&BlockId::hash(*block_hash)) {
Err(_) | Ok(BlockStatus::Unknown) | Ok(BlockStatus::Queued) => None,
@@ -333,7 +333,7 @@ fn run_collator_node<S, P, Extrinsic>(
}
};
let message_validator = polkadot_network::gossip::register_validator(
let message_validator = polkadot_network::legacy::gossip::register_validator(
network.clone(),
(is_known, client.clone()),
&spawner,
+3
View File
@@ -7,7 +7,9 @@ edition = "2018"
[dependencies]
arrayvec = "0.4.12"
bytes = "0.5"
parking_lot = "0.9.0"
derive_more = "0.14.1"
av_store = { package = "polkadot-availability-store", path = "../availability-store" }
polkadot-validation = { path = "../validation" }
polkadot-primitives = { path = "../primitives" }
@@ -20,6 +22,7 @@ sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkad
futures = "0.3.4"
log = "0.4.8"
exit-future = "0.2.0"
futures-timer = "2.0"
sc-client = { git = "https://github.com/paritytech/substrate", branch = "polkadot-master" }
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-master" }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-master" }
@@ -118,6 +118,7 @@ struct ParachainCollators {
}
/// Manages connected collators and role assignments from the perspective of a validator.
#[derive(Default)]
pub struct CollatorPool {
collators: HashMap<CollatorId, (ParaId, PeerId)>,
parachain_collators: HashMap<ParaId, ParachainCollators>,
@@ -38,7 +38,7 @@ use polkadot_primitives::Hash;
use std::collections::{HashMap, HashSet};
use log::warn;
use crate::router::attestation_topic;
use crate::legacy::router::attestation_topic;
use super::{cost, benefit, MAX_CHAIN_HEADS, LeavesVec,
ChainContext, Known, MessageValidationData, GossipStatement
@@ -210,8 +210,8 @@ impl View {
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::TestChainContext;
use crate::gossip::{Known, GossipParachainMessages};
use crate::legacy::tests::TestChainContext;
use crate::legacy::gossip::{Known, GossipParachainMessages};
use polkadot_primitives::parachain::Message as ParachainMessage;
fn hash(x: u8) -> Hash {
@@ -52,6 +52,7 @@
use sp_runtime::{generic::BlockId, traits::{BlakeTwo256, Hash as HashT}};
use sp_blockchain::Error as ClientError;
use sc_network::{config::Roles, Context, PeerId, ReputationChange};
use sc_network::{NetworkService as SubstrateNetworkService, specialization::NetworkSpecialization};
use sc_network_gossip::{
ValidationResult as GossipValidationResult,
ValidatorContext, MessageIntent,
@@ -73,8 +74,7 @@ use futures::prelude::*;
use parking_lot::RwLock;
use log::warn;
use super::PolkadotNetworkService;
use crate::{GossipMessageStream, NetworkService, PolkadotProtocol, router::attestation_topic};
use crate::legacy::{GossipMessageStream, NetworkService, GossipService, PolkadotProtocol, router::attestation_topic};
use attestation::{View as AttestationView, PeerData as AttestationPeerData};
use message_routing::{View as MessageRoutingView};
@@ -308,11 +308,11 @@ impl<F, P> ChainContext for (F, P) where
// NOTE: since RegisteredMessageValidator is meant to be a type-safe proof
// that we've actually done the registration, this should be the only way
// to construct it outside of tests.
pub fn register_validator<C: ChainContext + 'static>(
service: Arc<PolkadotNetworkService>,
pub fn register_validator<C: ChainContext + 'static, S: NetworkSpecialization<Block>>(
service: Arc<SubstrateNetworkService<Block, S, Hash>>,
chain: C,
executor: &impl futures::task::Spawn,
) -> RegisteredMessageValidator
) -> RegisteredMessageValidator<S>
{
let s = service.clone();
let report_handle = Box::new(move |peer: &PeerId, cost_benefit: ReputationChange| {
@@ -366,7 +366,7 @@ impl NewLeafActions {
/// Perform the queued actions, feeding into gossip.
pub fn perform(
self,
gossip: &dyn crate::NetworkService,
gossip: &dyn crate::legacy::GossipService,
) {
for action in self.actions {
match action {
@@ -382,16 +382,25 @@ impl NewLeafActions {
/// A registered message validator.
///
/// Create this using `register_validator`.
#[derive(Clone)]
pub struct RegisteredMessageValidator {
pub struct RegisteredMessageValidator<S: NetworkSpecialization<Block>> {
inner: Arc<MessageValidator<dyn ChainContext>>,
// Note: this is always `Some` in real code and `None` in tests.
service: Option<Arc<PolkadotNetworkService>>,
service: Option<Arc<SubstrateNetworkService<Block, S, Hash>>>,
// Note: this is always `Some` in real code and `None` in tests.
gossip_engine: Option<sc_network_gossip::GossipEngine<Block>>,
}
impl RegisteredMessageValidator {
impl<S: NetworkSpecialization<Block>> Clone for RegisteredMessageValidator<S> {
fn clone(&self) -> Self {
RegisteredMessageValidator {
inner: self.inner.clone(),
service: self.service.clone(),
gossip_engine: self.gossip_engine.clone(),
}
}
}
impl RegisteredMessageValidator<crate::legacy::PolkadotProtocol> {
#[cfg(test)]
pub(crate) fn new_test<C: ChainContext + 'static>(
chain: C,
@@ -405,7 +414,9 @@ impl RegisteredMessageValidator {
gossip_engine: None,
}
}
}
impl<S: NetworkSpecialization<Block>> RegisteredMessageValidator<S> {
pub fn register_availability_store(&mut self, availability_store: av_store::Store) {
self.inner.inner.write().availability_store = Some(availability_store);
}
@@ -469,10 +480,8 @@ impl RegisteredMessageValidator {
NewLeafActions { actions }
}
}
impl NetworkService for RegisteredMessageValidator {
fn gossip_messages_for(&self, topic: Hash) -> GossipMessageStream {
pub(crate) fn gossip_messages_for(&self, topic: Hash) -> GossipMessageStream {
let topic_stream = if let Some(gossip_engine) = self.gossip_engine.as_ref() {
gossip_engine.messages_for(topic)
} else {
@@ -483,7 +492,7 @@ impl NetworkService for RegisteredMessageValidator {
GossipMessageStream::new(topic_stream.boxed())
}
fn gossip_message(&self, topic: Hash, message: GossipMessage) {
pub(crate) fn gossip_message(&self, topic: Hash, message: GossipMessage) {
if let Some(gossip_engine) = self.gossip_engine.as_ref() {
gossip_engine.gossip_message(
topic,
@@ -495,14 +504,30 @@ impl NetworkService for RegisteredMessageValidator {
}
}
fn send_message(&self, who: PeerId, message: GossipMessage) {
pub(crate) fn send_message(&self, who: PeerId, message: GossipMessage) {
if let Some(gossip_engine) = self.gossip_engine.as_ref() {
gossip_engine.send_message(vec![who], message.encode());
} else {
log::error!("Called send_message on a test engine");
}
}
}
impl<S: NetworkSpecialization<Block>> GossipService for RegisteredMessageValidator<S> {
fn gossip_messages_for(&self, topic: Hash) -> GossipMessageStream {
RegisteredMessageValidator::gossip_messages_for(self, topic)
}
fn gossip_message(&self, topic: Hash, message: GossipMessage) {
RegisteredMessageValidator::gossip_message(self, topic, message)
}
fn send_message(&self, who: PeerId, message: GossipMessage) {
RegisteredMessageValidator::send_message(self, who, message)
}
}
impl NetworkService for RegisteredMessageValidator<crate::legacy::PolkadotProtocol> {
fn with_spec<F: Send + 'static>(&self, with: F)
where F: FnOnce(&mut PolkadotProtocol, &mut dyn Context<Block>)
{
@@ -806,7 +831,7 @@ mod tests {
use polkadot_validation::GenericStatement;
use super::message_routing::queue_topic;
use crate::tests::TestChainContext;
use crate::legacy::tests::TestChainContext;
#[derive(PartialEq, Clone, Debug)]
enum ContextEvent {
@@ -20,7 +20,7 @@
//! a validator changes his session key, or when they are generated.
use polkadot_primitives::{Hash, parachain::{ValidatorId}};
use crate::collator_pool::Role;
use crate::legacy::collator_pool::Role;
use std::collections::{HashMap, HashSet};
use std::time::Duration;
use wasm_timer::Instant;
@@ -39,6 +39,12 @@ pub struct LocalCollations<C> {
local_collations: HashMap<Hash, LocalCollation<C>>,
}
impl<C: Clone> Default for LocalCollations<C> {
fn default() -> Self {
Self::new()
}
}
impl<C: Clone> LocalCollations<C> {
/// Create a new `LocalCollations` tracker.
pub fn new() -> Self {
+790
View File
@@ -0,0 +1,790 @@
// Copyright 2017-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 <http://www.gnu.org/licenses/>.
//! Polkadot-specific network implementation.
//!
//! This manages routing for parachain statements, parachain block and outgoing message
//! data fetching, communication between collators and validators, and more.
pub mod collator_pool;
pub mod local_collations;
pub mod router;
pub mod validation;
pub mod gossip;
use codec::{Decode, Encode};
use futures::channel::oneshot;
use futures::prelude::*;
use polkadot_primitives::{Block, Hash, Header};
use polkadot_primitives::parachain::{
Id as ParaId, CollatorId, CandidateReceipt, Collation, PoVBlock,
StructuredUnroutedIngress, ValidatorId, OutgoingMessages, ErasureChunk,
};
use sc_network::{
PeerId, RequestId, Context, StatusMessage as GenericFullStatus,
specialization::NetworkSpecialization as Specialization,
};
use sc_network_gossip::TopicNotification;
use self::validation::{LiveValidationLeaves, RecentValidatorIds, InsertedRecentKey};
use self::collator_pool::{CollatorPool, Role, Action};
use self::local_collations::LocalCollations;
use log::{trace, debug, warn};
use std::collections::{HashMap, HashSet};
use std::pin::Pin;
use std::task::{Context as PollContext, Poll};
use self::gossip::{GossipMessage, ErasureChunkMessage, RegisteredMessageValidator};
use crate::{cost, benefit};
#[cfg(test)]
mod tests;
type FullStatus = GenericFullStatus<Block>;
/// Specialization of the network service for the polkadot protocol.
pub type PolkadotNetworkService = sc_network::NetworkService<Block, PolkadotProtocol, Hash>;
/// Basic gossip functionality that a network has to fulfill.
pub trait GossipService: Send + Sync + 'static {
/// Get a stream of gossip messages for a given hash.
fn gossip_messages_for(&self, topic: Hash) -> GossipMessageStream;
/// Gossip a message on given topic.
fn gossip_message(&self, topic: Hash, message: GossipMessage);
/// Send a message to a specific peer we're connected to.
fn send_message(&self, who: PeerId, message: GossipMessage);
}
/// Basic functionality that a network has to fulfill.
pub trait NetworkService: GossipService + Send + Sync + 'static {
/// Execute a closure with the polkadot protocol.
fn with_spec<F: Send + 'static>(&self, with: F)
where Self: Sized, F: FnOnce(&mut PolkadotProtocol, &mut dyn Context<Block>);
}
/// This is a newtype that implements a [`ProvideGossipMessages`] shim trait.
///
/// For any wrapped [`NetworkService`] type it implements a [`ProvideGossipMessages`].
/// For more details see documentation of [`ProvideGossipMessages`].
///
/// [`NetworkService`]: ./trait.NetworkService.html
/// [`ProvideGossipMessages`]: ../polkadot_availability_store/trait.ProvideGossipMessages.html
#[derive(Clone)]
pub struct AvailabilityNetworkShim(pub RegisteredMessageValidator<PolkadotProtocol>);
impl av_store::ProvideGossipMessages for AvailabilityNetworkShim {
fn gossip_messages_for(&self, topic: Hash)
-> Pin<Box<dyn Stream<Item = (Hash, Hash, ErasureChunk)> + Send>>
{
self.0.gossip_messages_for(topic)
.filter_map(|(msg, _)| async move {
match msg {
GossipMessage::ErasureChunk(chunk) => {
Some((chunk.relay_parent, chunk.candidate_hash, chunk.chunk))
},
_ => None,
}
})
.boxed()
}
fn gossip_erasure_chunk(
&self,
relay_parent: Hash,
candidate_hash: Hash,
erasure_root: Hash,
chunk: ErasureChunk
) {
let topic = av_store::erasure_coding_topic(relay_parent, erasure_root, chunk.index);
self.0.gossip_message(
topic,
GossipMessage::ErasureChunk(ErasureChunkMessage {
chunk,
relay_parent,
candidate_hash,
})
)
}
}
/// A stream of gossip messages and an optional sender for a topic.
pub struct GossipMessageStream {
topic_stream: Pin<Box<dyn Stream<Item = TopicNotification> + Send>>,
}
impl GossipMessageStream {
/// Create a new instance with the given topic stream.
pub fn new(topic_stream: Pin<Box<dyn Stream<Item = TopicNotification> + Send>>) -> Self {
Self {
topic_stream,
}
}
}
impl Stream for GossipMessageStream {
type Item = (GossipMessage, Option<PeerId>);
fn poll_next(self: Pin<&mut Self>, cx: &mut PollContext) -> Poll<Option<Self::Item>> {
let this = Pin::into_inner(self);
loop {
let msg = match Pin::new(&mut this.topic_stream).poll_next(cx) {
Poll::Ready(Some(msg)) => msg,
Poll::Ready(None) => return Poll::Ready(None),
Poll::Pending => return Poll::Pending,
};
debug!(target: "validation", "Processing statement for live validation leaf-work");
if let Ok(gmsg) = GossipMessage::decode(&mut &msg.message[..]) {
return Poll::Ready(Some((gmsg, msg.sender)))
}
}
}
}
/// Status of a Polkadot node.
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
pub struct Status {
collating_for: Option<(CollatorId, ParaId)>,
}
struct PoVBlockRequest {
attempted_peers: HashSet<ValidatorId>,
validation_leaf: Hash,
candidate_hash: Hash,
block_data_hash: Hash,
sender: oneshot::Sender<PoVBlock>,
canon_roots: StructuredUnroutedIngress,
}
impl PoVBlockRequest {
// Attempt to process a response. If the provided block is invalid,
// this returns an error result containing the unmodified request.
//
// If `Ok(())` is returned, that indicates that the request has been processed.
fn process_response(self, pov_block: PoVBlock) -> Result<(), Self> {
if pov_block.block_data.hash() != self.block_data_hash {
return Err(self);
}
match polkadot_validation::validate_incoming(&self.canon_roots, &pov_block.ingress) {
Ok(()) => {
let _ = self.sender.send(pov_block);
Ok(())
}
Err(_) => Err(self)
}
}
}
// ensures collator-protocol messages are sent in correct order.
// session key must be sent before collator role.
enum CollatorState {
Fresh,
RolePending(Role),
Primed(Option<Role>),
}
impl CollatorState {
fn send_key<F: FnMut(Message)>(&mut self, key: ValidatorId, mut f: F) {
f(Message::ValidatorId(key));
if let CollatorState::RolePending(role) = *self {
f(Message::CollatorRole(role));
*self = CollatorState::Primed(Some(role));
}
}
fn set_role<F: FnMut(Message)>(&mut self, role: Role, mut f: F) {
if let CollatorState::Primed(ref mut r) = *self {
f(Message::CollatorRole(role));
*r = Some(role);
} else {
*self = CollatorState::RolePending(role);
}
}
fn role(&self) -> Option<Role> {
match *self {
CollatorState::Fresh => None,
CollatorState::RolePending(role) => Some(role),
CollatorState::Primed(role) => role,
}
}
}
struct PeerInfo {
collating_for: Option<(CollatorId, ParaId)>,
validator_keys: RecentValidatorIds,
claimed_validator: bool,
collator_state: CollatorState,
}
impl PeerInfo {
fn should_send_key(&self) -> bool {
self.claimed_validator || self.collating_for.is_some()
}
}
/// Polkadot-specific messages.
#[derive(Debug, Encode, Decode)]
pub enum Message {
/// As a validator, tell the peer your current session key.
// TODO: do this with a cryptographic proof of some kind
// https://github.com/paritytech/polkadot/issues/47
ValidatorId(ValidatorId),
/// Requesting parachain proof-of-validation block (relay_parent, candidate_hash).
RequestPovBlock(RequestId, Hash, Hash),
/// Provide requested proof-of-validation block data by candidate hash or nothing if unknown.
PovBlock(RequestId, Option<PoVBlock>),
/// Tell a collator their role.
CollatorRole(Role),
/// A collation provided by a peer. Relay parent and collation.
Collation(Hash, Collation),
}
fn send_polkadot_message(ctx: &mut dyn Context<Block>, to: PeerId, message: Message) {
trace!(target: "p_net", "Sending polkadot message to {}: {:?}", to, message);
let encoded = message.encode();
ctx.send_chain_specific(to, encoded)
}
/// Polkadot protocol attachment for substrate.
pub struct PolkadotProtocol {
peers: HashMap<PeerId, PeerInfo>,
collating_for: Option<(CollatorId, ParaId)>,
collators: CollatorPool,
validators: HashMap<ValidatorId, PeerId>,
local_collations: LocalCollations<Collation>,
live_validation_leaves: LiveValidationLeaves,
in_flight: HashMap<(RequestId, PeerId), PoVBlockRequest>,
pending: Vec<PoVBlockRequest>,
availability_store: Option<av_store::Store>,
next_req_id: u64,
}
impl PolkadotProtocol {
/// Instantiate a polkadot protocol handler.
pub fn new(collating_for: Option<(CollatorId, ParaId)>) -> Self {
PolkadotProtocol {
peers: HashMap::new(),
collators: CollatorPool::new(),
collating_for,
validators: HashMap::new(),
local_collations: LocalCollations::new(),
live_validation_leaves: LiveValidationLeaves::new(),
in_flight: HashMap::new(),
pending: Vec::new(),
availability_store: None,
next_req_id: 1,
}
}
/// Fetch block data by candidate receipt.
fn fetch_pov_block(
&mut self,
ctx: &mut dyn Context<Block>,
candidate: &CandidateReceipt,
relay_parent: Hash,
canon_roots: StructuredUnroutedIngress,
) -> oneshot::Receiver<PoVBlock> {
let (tx, rx) = oneshot::channel();
self.pending.push(PoVBlockRequest {
attempted_peers: Default::default(),
validation_leaf: relay_parent,
candidate_hash: candidate.hash(),
block_data_hash: candidate.block_data_hash,
sender: tx,
canon_roots,
});
self.dispatch_pending_requests(ctx);
rx
}
/// Note new leaf to do validation work at
fn new_validation_leaf_work(
&mut self,
ctx: &mut dyn Context<Block>,
params: validation::LeafWorkParams,
) -> validation::LiveValidationLeaf {
let (work, new_local) = self.live_validation_leaves
.new_validation_leaf(params);
if let Some(new_local) = new_local {
for (id, peer_data) in self.peers.iter_mut()
.filter(|&(_, ref info)| info.should_send_key())
{
peer_data.collator_state.send_key(new_local.clone(), |msg| send_polkadot_message(
ctx,
id.clone(),
msg
));
}
}
work
}
// true indicates that it was removed actually.
fn remove_validation_session(&mut self, parent_hash: Hash) -> bool {
self.live_validation_leaves.remove(parent_hash)
}
fn dispatch_pending_requests(&mut self, ctx: &mut dyn Context<Block>) {
let mut new_pending = Vec::new();
let validator_keys = &mut self.validators;
let next_req_id = &mut self.next_req_id;
let in_flight = &mut self.in_flight;
for mut pending in ::std::mem::replace(&mut self.pending, Vec::new()) {
let parent = pending.validation_leaf;
let c_hash = pending.candidate_hash;
let still_pending = self.live_validation_leaves.with_pov_block(&parent, &c_hash, |x| match x {
Ok(data @ &_) => {
// answer locally.
let _ = pending.sender.send(data.clone());
None
}
Err(Some(known_keys)) => {
let next_peer = known_keys.iter()
.filter_map(|x| validator_keys.get(x).map(|id| (x.clone(), id.clone())))
.find(|&(ref key, _)| pending.attempted_peers.insert(key.clone()))
.map(|(_, id)| id);
// dispatch to peer
if let Some(who) = next_peer {
let req_id = *next_req_id;
*next_req_id += 1;
send_polkadot_message(
ctx,
who.clone(),
Message::RequestPovBlock(req_id, parent, c_hash),
);
in_flight.insert((req_id, who), pending);
None
} else {
Some(pending)
}
}
Err(None) => None, // no such known validation leaf-work. prune out.
});
if let Some(pending) = still_pending {
new_pending.push(pending);
}
}
self.pending = new_pending;
}
fn on_polkadot_message(&mut self, ctx: &mut dyn Context<Block>, who: PeerId, msg: Message) {
trace!(target: "p_net", "Polkadot message from {}: {:?}", who, msg);
match msg {
Message::ValidatorId(key) => self.on_session_key(ctx, who, key),
Message::RequestPovBlock(req_id, relay_parent, candidate_hash) => {
let pov_block = self.live_validation_leaves.with_pov_block(
&relay_parent,
&candidate_hash,
|res| res.ok().map(|b| b.clone()),
);
send_polkadot_message(ctx, who, Message::PovBlock(req_id, pov_block));
}
Message::PovBlock(req_id, data) => self.on_pov_block(ctx, who, req_id, data),
Message::Collation(relay_parent, collation) => self.on_collation(ctx, who, relay_parent, collation),
Message::CollatorRole(role) => self.on_new_role(ctx, who, role),
}
}
fn on_session_key(&mut self, ctx: &mut dyn Context<Block>, who: PeerId, key: ValidatorId) {
{
let info = match self.peers.get_mut(&who) {
Some(peer) => peer,
None => {
trace!(target: "p_net", "Network inconsistency: message received from unconnected peer {}", who);
return
}
};
if !info.claimed_validator {
ctx.report_peer(who, cost::UNEXPECTED_MESSAGE);
return;
}
ctx.report_peer(who.clone(), benefit::EXPECTED_MESSAGE);
let local_collations = &mut self.local_collations;
let new_collations = match info.validator_keys.insert(key.clone()) {
InsertedRecentKey::AlreadyKnown => Vec::new(),
InsertedRecentKey::New(Some(old_key)) => {
self.validators.remove(&old_key);
local_collations.fresh_key(&old_key, &key)
}
InsertedRecentKey::New(None) => info.collator_state.role()
.map(|r| local_collations.note_validator_role(key.clone(), r))
.unwrap_or_else(Vec::new),
};
for (relay_parent, collation) in new_collations {
send_polkadot_message(
ctx,
who.clone(),
Message::Collation(relay_parent, collation),
)
}
self.validators.insert(key, who);
}
self.dispatch_pending_requests(ctx);
}
fn on_pov_block(
&mut self,
ctx: &mut dyn Context<Block>,
who: PeerId,
req_id: RequestId,
pov_block: Option<PoVBlock>,
) {
match self.in_flight.remove(&(req_id, who.clone())) {
Some(mut req) => {
match pov_block {
Some(pov_block) => {
match req.process_response(pov_block) {
Ok(()) => {
ctx.report_peer(who, benefit::GOOD_POV_BLOCK);
return;
}
Err(r) => {
ctx.report_peer(who, cost::BAD_POV_BLOCK);
req = r;
}
}
},
None => {
ctx.report_peer(who, benefit::EXPECTED_MESSAGE);
}
}
self.pending.push(req);
self.dispatch_pending_requests(ctx);
}
None => ctx.report_peer(who, cost::UNEXPECTED_MESSAGE),
}
}
// when a validator sends us (a collator) a new role.
fn on_new_role(&mut self, ctx: &mut dyn Context<Block>, who: PeerId, role: Role) {
let info = match self.peers.get_mut(&who) {
Some(peer) => peer,
None => {
trace!(target: "p_net", "Network inconsistency: message received from unconnected peer {}", who);
return
}
};
debug!(target: "p_net", "New collator role {:?} from {}", role, who);
if info.validator_keys.as_slice().is_empty() {
ctx.report_peer(who, cost::UNEXPECTED_ROLE)
} else {
// update role for all saved session keys for this validator.
let local_collations = &mut self.local_collations;
for (relay_parent, collation) in info.validator_keys
.as_slice()
.iter()
.cloned()
.flat_map(|k| local_collations.note_validator_role(k, role))
{
debug!(target: "p_net", "Broadcasting collation on relay parent {:?}", relay_parent);
send_polkadot_message(
ctx,
who.clone(),
Message::Collation(relay_parent, collation),
)
}
}
}
/// Convert the given `CollatorId` to a `PeerId`.
pub fn collator_id_to_peer_id(&self, collator_id: &CollatorId) -> Option<&PeerId> {
self.collators.collator_id_to_peer_id(collator_id)
}
}
impl Specialization<Block> for PolkadotProtocol {
fn status(&self) -> Vec<u8> {
Status { collating_for: self.collating_for.clone() }.encode()
}
fn on_connect(&mut self, ctx: &mut dyn Context<Block>, who: PeerId, status: FullStatus) {
let local_status = Status::decode(&mut &status.chain_status[..])
.unwrap_or_else(|_| Status { collating_for: None });
let validator = status.roles.contains(sc_network::config::Roles::AUTHORITY);
let mut peer_info = PeerInfo {
collating_for: local_status.collating_for.clone(),
validator_keys: Default::default(),
claimed_validator: validator,
collator_state: CollatorState::Fresh,
};
if let Some((ref acc_id, ref para_id)) = local_status.collating_for {
if self.collator_peer(acc_id.clone()).is_some() {
ctx.report_peer(who, cost::COLLATOR_ALREADY_KNOWN);
return
}
ctx.report_peer(who.clone(), benefit::NEW_COLLATOR);
let collator_role = self.collators.on_new_collator(
acc_id.clone(),
para_id.clone(),
who.clone(),
);
peer_info.collator_state.set_role(collator_role, |msg| send_polkadot_message(
ctx,
who.clone(),
msg,
));
}
// send session keys.
if peer_info.should_send_key() {
for local_session_key in self.live_validation_leaves.recent_keys() {
peer_info.collator_state.send_key(local_session_key.clone(), |msg| send_polkadot_message(
ctx,
who.clone(),
msg,
));
}
}
self.peers.insert(who, peer_info);
self.dispatch_pending_requests(ctx);
}
fn on_disconnect(&mut self, ctx: &mut dyn Context<Block>, who: PeerId) {
if let Some(info) = self.peers.remove(&who) {
if let Some((acc_id, _)) = info.collating_for {
let new_primary = self.collators.on_disconnect(acc_id)
.and_then(|new_primary| self.collator_peer(new_primary));
if let Some((new_primary, primary_info)) = new_primary {
primary_info.collator_state.set_role(Role::Primary, |msg| send_polkadot_message(
ctx,
new_primary.clone(),
msg,
));
}
}
for key in info.validator_keys.as_slice().iter() {
self.validators.remove(key);
self.local_collations.on_disconnect(key);
}
{
let pending = &mut self.pending;
self.in_flight.retain(|&(_, ref peer), val| {
let retain = peer != &who;
if !retain {
// swap with a dummy value which will be dropped immediately.
let (sender, _) = oneshot::channel();
pending.push(::std::mem::replace(val, PoVBlockRequest {
attempted_peers: Default::default(),
validation_leaf: Default::default(),
candidate_hash: Default::default(),
block_data_hash: Default::default(),
canon_roots: StructuredUnroutedIngress(Vec::new()),
sender,
}));
}
retain
});
}
self.dispatch_pending_requests(ctx);
}
}
fn on_message(
&mut self,
ctx: &mut dyn Context<Block>,
who: PeerId,
message: Vec<u8>,
) {
match Message::decode(&mut &message[..]) {
Ok(msg) => {
ctx.report_peer(who.clone(), benefit::VALID_FORMAT);
self.on_polkadot_message(ctx, who, msg)
},
Err(_) => {
trace!(target: "p_net", "Bad message from {}", who);
ctx.report_peer(who, cost::INVALID_FORMAT);
}
}
}
fn maintain_peers(&mut self, ctx: &mut dyn Context<Block>) {
self.collators.collect_garbage(None);
self.local_collations.collect_garbage(None);
self.dispatch_pending_requests(ctx);
for collator_action in self.collators.maintain_peers() {
match collator_action {
Action::Disconnect(collator) => self.disconnect_bad_collator(ctx, collator),
Action::NewRole(account_id, role) => if let Some((collator, info)) = self.collator_peer(account_id) {
info.collator_state.set_role(role, |msg| send_polkadot_message(
ctx,
collator.clone(),
msg,
))
},
}
}
}
fn on_block_imported(&mut self, _ctx: &mut dyn Context<Block>, hash: Hash, header: &Header) {
self.collators.collect_garbage(Some(&hash));
self.local_collations.collect_garbage(Some(&header.parent_hash));
}
}
impl PolkadotProtocol {
// we received a collation from a peer
fn on_collation(
&mut self,
ctx: &mut dyn Context<Block>,
from: PeerId,
relay_parent: Hash,
collation: Collation
) {
let collation_para = collation.info.parachain_index;
let collated_acc = collation.info.collator.clone();
match self.peers.get(&from) {
None => ctx.report_peer(from, cost::UNKNOWN_PEER),
Some(peer_info) => {
ctx.report_peer(from.clone(), benefit::KNOWN_PEER);
match peer_info.collating_for {
None => ctx.report_peer(from, cost::UNEXPECTED_MESSAGE),
Some((ref acc_id, ref para_id)) => {
ctx.report_peer(from.clone(), benefit::EXPECTED_MESSAGE);
let structurally_valid = para_id == &collation_para && acc_id == &collated_acc;
if structurally_valid && collation.info.check_signature().is_ok() {
debug!(target: "p_net", "Received collation for parachain {:?} from peer {}", para_id, from);
ctx.report_peer(from, benefit::GOOD_COLLATION);
self.collators.on_collation(acc_id.clone(), relay_parent, collation)
} else {
ctx.report_peer(from, cost::INVALID_FORMAT)
};
}
}
},
}
}
fn await_collation(&mut self, relay_parent: Hash, para_id: ParaId) -> oneshot::Receiver<Collation> {
let (tx, rx) = oneshot::channel();
debug!(target: "p_net", "Attempting to get collation for parachain {:?} on relay parent {:?}", para_id, relay_parent);
self.collators.await_collation(relay_parent, para_id, tx);
rx
}
// get connected peer with given account ID for collation.
fn collator_peer(&mut self, collator_id: CollatorId) -> Option<(PeerId, &mut PeerInfo)> {
let check_info = |info: &PeerInfo| info
.collating_for
.as_ref()
.map_or(false, |&(ref acc_id, _)| acc_id == &collator_id);
self.peers
.iter_mut()
.filter(|&(_, ref info)| check_info(&**info))
.map(|(who, info)| (who.clone(), info))
.next()
}
// disconnect a collator by account-id.
fn disconnect_bad_collator(&mut self, ctx: &mut dyn Context<Block>, collator_id: CollatorId) {
if let Some((who, _)) = self.collator_peer(collator_id) {
ctx.report_peer(who, cost::BAD_COLLATION)
}
}
}
impl PolkadotProtocol {
/// Add a local collation and broadcast it to the necessary peers.
///
/// This should be called by a collator intending to get the locally-collated
/// block into the hands of validators.
/// It also places the outgoing message and block data in the local availability store.
pub fn add_local_collation(
&mut self,
ctx: &mut dyn Context<Block>,
relay_parent: Hash,
targets: HashSet<ValidatorId>,
collation: Collation,
outgoing_targeted: OutgoingMessages,
) -> impl Future<Output = ()> {
debug!(target: "p_net", "Importing local collation on relay parent {:?} and parachain {:?}",
relay_parent, collation.info.parachain_index);
for (primary, cloned_collation) in self.local_collations.add_collation(relay_parent, targets, collation.clone()) {
match self.validators.get(&primary) {
Some(who) => {
debug!(target: "p_net", "Sending local collation to {:?}", primary);
send_polkadot_message(
ctx,
who.clone(),
Message::Collation(relay_parent, cloned_collation),
)
},
None =>
warn!(target: "polkadot_network", "Encountered tracked but disconnected validator {:?}", primary),
}
}
let availability_store = self.availability_store.clone();
let collation_cloned = collation.clone();
async move {
if let Some(availability_store) = availability_store {
let _ = availability_store.make_available(av_store::Data {
relay_parent,
parachain_id: collation_cloned.info.parachain_index,
block_data: collation_cloned.pov.block_data.clone(),
outgoing_queues: Some(outgoing_targeted.clone().into()),
}).await;
}
}
}
/// Give the network protocol a handle to an availability store, used for
/// circulation of parachain data required for validation.
pub fn register_availability_store(&mut self, availability_store: ::av_store::Store) {
self.availability_store = Some(availability_store);
}
}
@@ -31,7 +31,6 @@ use polkadot_primitives::{Block, Hash};
use polkadot_primitives::parachain::{
OutgoingMessages, CandidateReceipt, ParachainHost, ValidatorIndex, Collation, PoVBlock, ErasureChunk,
};
use crate::gossip::{RegisteredMessageValidator, GossipMessage, GossipStatement, ErasureChunkMessage};
use sp_api::ProvideRuntimeApi;
use futures::prelude::*;
@@ -44,8 +43,9 @@ use std::io;
use std::sync::Arc;
use std::pin::Pin;
use crate::validation::{LeafWorkDataFetcher, Executor};
use crate::NetworkService;
use crate::legacy::gossip::{RegisteredMessageValidator, GossipMessage, GossipStatement, ErasureChunkMessage};
use crate::legacy::validation::{LeafWorkDataFetcher, Executor};
use crate::legacy::{NetworkService, PolkadotProtocol};
/// Compute the gossip topic for attestations on the given parent hash.
pub(crate) fn attestation_topic(parent_hash: Hash) -> Hash {
@@ -78,14 +78,16 @@ pub struct Router<P, T> {
attestation_topic: Hash,
fetcher: LeafWorkDataFetcher<P, T>,
deferred_statements: Arc<Mutex<DeferredStatements>>,
message_validator: RegisteredMessageValidator,
message_validator: RegisteredMessageValidator<PolkadotProtocol>,
drop_signal: Arc<exit_future::Signal>,
}
impl<P, T> Router<P, T> {
pub(crate) fn new(
table: Arc<SharedTable>,
fetcher: LeafWorkDataFetcher<P, T>,
message_validator: RegisteredMessageValidator,
message_validator: RegisteredMessageValidator<PolkadotProtocol>,
drop_signal: exit_future::Signal,
) -> Self {
let parent_hash = fetcher.parent_hash();
Router {
@@ -94,6 +96,7 @@ impl<P, T> Router<P, T> {
attestation_topic: attestation_topic(parent_hash),
deferred_statements: Arc::new(Mutex::new(DeferredStatements::new())),
message_validator,
drop_signal: Arc::new(drop_signal),
}
}
@@ -111,7 +114,7 @@ impl<P, T> Router<P, T> {
self.fetcher.parent_hash()
}
fn network(&self) -> &RegisteredMessageValidator {
fn network(&self) -> &RegisteredMessageValidator<PolkadotProtocol> {
self.fetcher.network()
}
}
@@ -124,6 +127,7 @@ impl<P, T: Clone> Clone for Router<P, T> {
attestation_topic: self.attestation_topic,
deferred_statements: self.deferred_statements.clone(),
message_validator: self.message_validator.clone(),
drop_signal: self.drop_signal.clone(),
}
}
}
@@ -155,7 +159,7 @@ impl<P: ProvideRuntimeApi<Block> + Send + Sync + 'static, T> Router<P, T> where
// import all statements pending on this candidate
let (mut statements, _traces) = if let GenericStatement::Candidate(_) = statement.statement {
self.deferred_statements.lock().get_deferred(&c_hash)
self.deferred_statements.lock().take_deferred(&c_hash)
} else {
(Vec::new(), Vec::new())
};
@@ -229,6 +233,7 @@ impl<P: ProvideRuntimeApi<Block> + Send, T> TableRouter for Router<P, T> where
T: Clone + Executor + Send + 'static,
{
type Error = io::Error;
type SendLocalCollation = future::Ready<Result<(), Self::Error>>;
type FetchValidationProof = Pin<Box<dyn Future<Output = Result<PoVBlock, io::Error>> + Send>>;
// We have fetched from a collator and here the receipt should have been already formed.
@@ -238,7 +243,7 @@ impl<P: ProvideRuntimeApi<Block> + Send, T> TableRouter for Router<P, T> where
receipt: CandidateReceipt,
outgoing: OutgoingMessages,
chunks: (ValidatorIndex, &[ErasureChunk])
) {
) -> Self::SendLocalCollation {
// produce a signed statement
let hash = receipt.hash();
let erasure_root = receipt.erasure_root;
@@ -251,7 +256,7 @@ impl<P: ProvideRuntimeApi<Block> + Send, T> TableRouter for Router<P, T> where
let statement = GossipStatement::new(
self.parent_hash(),
match self.table.import_validated(validated) {
None => return,
None => return future::ready(Ok(())),
Some(s) => s,
},
);
@@ -273,6 +278,8 @@ impl<P: ProvideRuntimeApi<Block> + Send, T> TableRouter for Router<P, T> where
message.into()
);
}
future::ready(Ok(()))
}
fn fetch_pov_block(&self, candidate: &CandidateReceipt) -> Self::FetchValidationProof {
@@ -289,26 +296,29 @@ impl<P, T> Drop for Router<P, T> {
// A unique trace for valid statements issued by a validator.
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
enum StatementTrace {
pub(crate) enum StatementTrace {
Valid(ValidatorIndex, Hash),
Invalid(ValidatorIndex, Hash),
}
// helper for deferring statements whose associated candidate is unknown.
struct DeferredStatements {
/// Helper for deferring statements whose associated candidate is unknown.
pub(crate) struct DeferredStatements {
deferred: HashMap<Hash, Vec<SignedStatement>>,
known_traces: HashSet<StatementTrace>,
}
impl DeferredStatements {
fn new() -> Self {
/// Create a new `DeferredStatements`.
pub(crate) fn new() -> Self {
DeferredStatements {
deferred: HashMap::new(),
known_traces: HashSet::new(),
}
}
fn push(&mut self, statement: SignedStatement) {
/// Push a new statement onto the deferred pile. `Candidate` statements
/// cannot be deferred and are ignored.
pub(crate) fn push(&mut self, statement: SignedStatement) {
let (hash, trace) = match statement.statement {
GenericStatement::Candidate(_) => return,
GenericStatement::Valid(hash) => (hash, StatementTrace::Valid(statement.sender.clone(), hash)),
@@ -320,7 +330,8 @@ impl DeferredStatements {
}
}
fn get_deferred(&mut self, hash: &Hash) -> (Vec<SignedStatement>, Vec<StatementTrace>) {
/// Take all deferred statements referencing the given candidate hash out.
pub(crate) fn take_deferred(&mut self, hash: &Hash) -> (Vec<SignedStatement>, Vec<StatementTrace>) {
match self.deferred.remove(hash) {
None => (Vec::new(), Vec::new()),
Some(deferred) => {
@@ -361,7 +372,7 @@ mod tests {
// pre-push.
{
let (signed, traces) = deferred.get_deferred(&hash);
let (signed, traces) = deferred.take_deferred(&hash);
assert!(signed.is_empty());
assert!(traces.is_empty());
}
@@ -371,7 +382,7 @@ mod tests {
// draining: second push should have been ignored.
{
let (signed, traces) = deferred.get_deferred(&hash);
let (signed, traces) = deferred.take_deferred(&hash);
assert_eq!(signed.len(), 1);
assert_eq!(traces.len(), 1);
@@ -381,7 +392,7 @@ mod tests {
// after draining
{
let (signed, traces) = deferred.get_deferred(&hash);
let (signed, traces) = deferred.take_deferred(&hash);
assert!(signed.is_empty());
assert!(traces.is_empty());
}
@@ -18,7 +18,7 @@
use std::collections::HashMap;
use super::{PolkadotProtocol, Status, Message, FullStatus};
use crate::validation::LeafWorkParams;
use crate::legacy::validation::LeafWorkParams;
use polkadot_validation::GenericStatement;
use polkadot_primitives::{Block, Hash};
@@ -74,12 +74,12 @@ impl TestContext {
#[derive(Default)]
pub struct TestChainContext {
pub known_map: HashMap<Hash, crate::gossip::Known>,
pub known_map: HashMap<Hash, crate::legacy::gossip::Known>,
pub ingress_roots: HashMap<Hash, Vec<Hash>>,
}
impl crate::gossip::ChainContext for TestChainContext {
fn is_known(&self, block_hash: &Hash) -> Option<crate::gossip::Known> {
impl crate::legacy::gossip::ChainContext for TestChainContext {
fn is_known(&self, block_hash: &Hash) -> Option<crate::legacy::gossip::Known> {
self.known_map.get(block_hash).map(|x| x.clone())
}
@@ -18,12 +18,12 @@
#![allow(unused)]
use crate::gossip::GossipMessage;
use crate::legacy::gossip::GossipMessage;
use sc_network::{Context as NetContext, PeerId};
use sc_network_gossip::TopicNotification;
use sp_core::{NativeOrEncoded, ExecutionContext};
use sp_keyring::Sr25519Keyring;
use crate::{PolkadotProtocol, NetworkService, GossipMessageStream};
use crate::legacy::{PolkadotProtocol, NetworkService, GossipService, GossipMessageStream};
use polkadot_validation::{SharedTable, Network};
use polkadot_primitives::{Block, BlockNumber, Hash, Header, BlockId};
@@ -117,6 +117,18 @@ struct TestNetwork {
}
impl NetworkService for TestNetwork {
fn with_spec<F: Send + 'static>(&self, with: F)
where F: FnOnce(&mut PolkadotProtocol, &mut dyn NetContext<Block>)
{
let mut context = TestContext::default();
let res = with(&mut *self.proto.lock(), &mut context);
// TODO: send context to worker for message routing.
// https://github.com/paritytech/polkadot/issues/215
res
}
}
impl GossipService for TestNetwork {
fn gossip_messages_for(&self, topic: Hash) -> GossipMessageStream {
let (tx, rx) = mpsc::unbounded();
let _ = self.gossip.send_listener.unbounded_send((topic, tx));
@@ -131,16 +143,6 @@ impl NetworkService for TestNetwork {
let notification = TopicNotification { message: message.encode(), sender: None };
let _ = self.gossip.send_message.unbounded_send((topic, notification));
}
fn with_spec<F: Send + 'static>(&self, with: F)
where F: FnOnce(&mut PolkadotProtocol, &mut dyn NetContext<Block>)
{
let mut context = TestContext::default();
let res = with(&mut *self.proto.lock(), &mut context);
// TODO: send context to worker for message routing.
// https://github.com/paritytech/polkadot/issues/215
res
}
}
#[derive(Default)]
@@ -319,7 +321,7 @@ impl ParachainHost<Block> for RuntimeApi {
}
}
type TestValidationNetwork<SP> = crate::validation::ValidationNetwork<TestApi, SP>;
type TestValidationNetwork<SP> = crate::legacy::validation::ValidationNetwork<TestApi, SP>;
struct Built<SP> {
gossip: Pin<Box<dyn Future<Output = ()>>>,
@@ -327,7 +329,8 @@ struct Built<SP> {
networks: Vec<TestValidationNetwork<SP>>,
}
fn build_network<SP: Spawn + Clone>(n: usize, spawner: SP) -> Built<SP> {
fn build_network<SP: Spawn + Clone>(n: usize, spawner: SP)-> Built<SP> {
use crate::legacy::gossip::RegisteredMessageValidator;
let (gossip_router, gossip_handle) = make_gossip();
let api_handle = Arc::new(Mutex::new(Default::default()));
let runtime_api = Arc::new(TestApi { data: api_handle.clone() });
@@ -338,7 +341,7 @@ fn build_network<SP: Spawn + Clone>(n: usize, spawner: SP) -> Built<SP> {
gossip: gossip_handle.clone(),
});
let message_val = crate::gossip::RegisteredMessageValidator::new_test(
let message_val = RegisteredMessageValidator::<PolkadotProtocol>::new_test(
TestChainContext::default(),
Box::new(|_, _| {}),
);
@@ -44,10 +44,10 @@ use std::pin::Pin;
use arrayvec::ArrayVec;
use parking_lot::Mutex;
use crate::router::Router;
use crate::gossip::{RegisteredMessageValidator, MessageValidationData};
use crate::legacy::router::Router;
use crate::legacy::gossip::{RegisteredMessageValidator, MessageValidationData};
use super::NetworkService;
use super::{NetworkService, PolkadotProtocol};
pub use polkadot_validation::Incoming;
@@ -65,13 +65,13 @@ pub struct LeafWorkParams {
pub struct ValidationNetwork<P, T> {
api: Arc<P>,
executor: T,
network: RegisteredMessageValidator,
network: RegisteredMessageValidator<PolkadotProtocol>,
}
impl<P, T> ValidationNetwork<P, T> {
/// Create a new consensus networking object.
pub fn new(
network: RegisteredMessageValidator,
network: RegisteredMessageValidator<PolkadotProtocol>,
api: Arc<P>,
executor: T,
) -> Self {
@@ -165,7 +165,7 @@ impl<P, T> ValidationNetwork<P, T> {
/// dropped when it is not required anymore. Otherwise, it will stick around in memory
/// infinitely.
pub fn checked_statements(&self, relay_parent: Hash) -> impl Stream<Item=SignedStatement> {
crate::router::checked_statements(&self.network, crate::router::attestation_topic(relay_parent))
crate::legacy::router::checked_statements(&self.network, crate::legacy::router::attestation_topic(relay_parent))
}
}
@@ -179,12 +179,12 @@ impl<P, T> ParachainNetwork for ValidationNetwork<P, T> where
type TableRouter = Router<P, T>;
type BuildTableRouter = Box<dyn Future<Output=Result<Self::TableRouter, String>> + Send + Unpin>;
fn communication_for(
fn build_table_router(
&self,
table: Arc<SharedTable>,
authorities: &[ValidatorId],
exit: exit_future::Exit,
) -> Self::BuildTableRouter {
let (signal, exit) = exit_future::signal();
let parent_hash = *table.consensus_parent_hash();
let local_session_key = table.session_key();
@@ -203,6 +203,7 @@ impl<P, T> ParachainNetwork for ValidationNetwork<P, T> where
table,
fetcher,
network,
signal,
);
let table_router_clone = table_router.clone();
@@ -390,7 +391,7 @@ impl RecentValidatorIds {
InsertedRecentKey::New(old)
}
/// As a slice.
/// As a slice. Most recent is last.
pub(crate) fn as_slice(&self) -> &[ValidatorId] {
&*self.inner
}
@@ -512,7 +513,7 @@ impl LiveValidationLeaves {
/// Can fetch data for a given validation leaf-work instance.
pub struct LeafWorkDataFetcher<P, T> {
network: RegisteredMessageValidator,
network: RegisteredMessageValidator<PolkadotProtocol>,
api: Arc<P>,
task_executor: T,
knowledge: Arc<Mutex<Knowledge>>,
@@ -531,7 +532,7 @@ impl<P, T> LeafWorkDataFetcher<P, T> {
}
/// Get the network service.
pub(crate) fn network(&self) -> &RegisteredMessageValidator {
pub(crate) fn network(&self) -> &RegisteredMessageValidator<PolkadotProtocol> {
&self.network
}
+12 -764
View File
@@ -1,4 +1,4 @@
// Copyright 2017-2020 Parity Technologies (UK) Ltd.
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
@@ -14,43 +14,23 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Polkadot-specific network implementation.
//! High-level network protocols for Polkadot.
//!
//! This manages routing for parachain statements, parachain block and outgoing message
//! data fetching, communication between collators and validators, and more.
mod collator_pool;
mod local_collations;
mod router;
pub mod validation;
pub mod gossip;
use polkadot_primitives::{Block, Hash};
use codec::{Decode, Encode};
use futures::channel::oneshot;
use futures::prelude::*;
use polkadot_primitives::{Block, Hash, Header};
use polkadot_primitives::parachain::{
Id as ParaId, CollatorId, CandidateReceipt, Collation, PoVBlock,
StructuredUnroutedIngress, ValidatorId, OutgoingMessages, ErasureChunk,
};
use sc_network::{
PeerId, RequestId, Context, StatusMessage as GenericFullStatus,
specialization::NetworkSpecialization as Specialization,
};
use sc_network_gossip::TopicNotification;
use self::validation::{LiveValidationLeaves, RecentValidatorIds, InsertedRecentKey};
use self::collator_pool::{CollatorPool, Role, Action};
use self::local_collations::LocalCollations;
use log::{trace, debug, warn};
pub mod legacy;
pub mod protocol;
use std::collections::{HashMap, HashSet};
use std::pin::Pin;
use std::task::{Context as PollContext, Poll};
sc_network::construct_simple_protocol! {
/// Stub until https://github.com/paritytech/substrate/pull/4665 is merged
pub struct PolkadotProtocol where Block = Block { }
}
use crate::gossip::{GossipMessage, ErasureChunkMessage, RegisteredMessageValidator};
#[cfg(test)]
mod tests;
/// Specialization of the network service for the polkadot protocol.
pub type PolkadotNetworkService = sc_network::NetworkService<Block, PolkadotProtocol, Hash>;
mod cost {
use sc_network::ReputationChange as Rep;
@@ -59,7 +39,7 @@ mod cost {
pub(super) const INVALID_FORMAT: Rep = Rep::new(-200, "Polkadot: Bad message");
pub(super) const UNKNOWN_PEER: Rep = Rep::new(-50, "Polkadot: Unknown peer");
pub(super) const COLLATOR_ALREADY_KNOWN: Rep = Rep::new( -100, "Polkadot: Known collator");
pub(super) const COLLATOR_ALREADY_KNOWN: Rep = Rep::new(-100, "Polkadot: Known collator");
pub(super) const BAD_COLLATION: Rep = Rep::new(-1000, "Polkadot: Bad collation");
pub(super) const BAD_POV_BLOCK: Rep = Rep::new(-1000, "Polkadot: Bad POV block");
}
@@ -75,735 +55,3 @@ mod benefit {
pub(super) const GOOD_POV_BLOCK: Rep = Rep::new(100, "Polkadot: Good POV block");
}
type FullStatus = GenericFullStatus<Block>;
/// Specialization of the network service for the polkadot protocol.
pub type PolkadotNetworkService = sc_network::NetworkService<Block, PolkadotProtocol, Hash>;
/// Basic functionality that a network has to fulfill.
pub trait NetworkService: Send + Sync + 'static {
/// Get a stream of gossip messages for a given hash.
fn gossip_messages_for(&self, topic: Hash) -> GossipMessageStream;
/// Gossip a message on given topic.
fn gossip_message(&self, topic: Hash, message: GossipMessage);
/// Send a message to a specific peer we're connected to.
fn send_message(&self, who: PeerId, message: GossipMessage);
/// Execute a closure with the polkadot protocol.
fn with_spec<F: Send + 'static>(&self, with: F)
where Self: Sized, F: FnOnce(&mut PolkadotProtocol, &mut dyn Context<Block>);
}
/// This is a newtype that implements a [`ProvideGossipMessages`] shim trait.
///
/// For any wrapped [`NetworkService`] type it implements a [`ProvideGossipMessages`].
/// For more details see documentation of [`ProvideGossipMessages`].
///
/// [`NetworkService`]: ./trait.NetworkService.html
/// [`ProvideGossipMessages`]: ../polkadot_availability_store/trait.ProvideGossipMessages.html
#[derive(Clone)]
pub struct AvailabilityNetworkShim(pub RegisteredMessageValidator);
impl av_store::ProvideGossipMessages for AvailabilityNetworkShim {
fn gossip_messages_for(&self, topic: Hash)
-> Pin<Box<dyn Stream<Item = (Hash, Hash, ErasureChunk)> + Send>>
{
self.0.gossip_messages_for(topic)
.filter_map(|(msg, _)| async move {
match msg {
GossipMessage::ErasureChunk(chunk) => {
Some((chunk.relay_parent, chunk.candidate_hash, chunk.chunk))
},
_ => None,
}
})
.boxed()
}
fn gossip_erasure_chunk(
&self,
relay_parent: Hash,
candidate_hash: Hash,
erasure_root: Hash,
chunk: ErasureChunk
) {
let topic = av_store::erasure_coding_topic(relay_parent, erasure_root, chunk.index);
self.0.gossip_message(
topic,
GossipMessage::ErasureChunk(ErasureChunkMessage {
chunk,
relay_parent,
candidate_hash,
})
)
}
}
/// A stream of gossip messages and an optional sender for a topic.
pub struct GossipMessageStream {
topic_stream: Pin<Box<dyn Stream<Item = TopicNotification> + Send>>,
}
impl GossipMessageStream {
/// Create a new instance with the given topic stream.
pub fn new(topic_stream: Pin<Box<dyn Stream<Item = TopicNotification> + Send>>) -> Self {
Self {
topic_stream,
}
}
}
impl Stream for GossipMessageStream {
type Item = (GossipMessage, Option<PeerId>);
fn poll_next(self: Pin<&mut Self>, cx: &mut PollContext) -> Poll<Option<Self::Item>> {
let this = Pin::into_inner(self);
loop {
let msg = match Pin::new(&mut this.topic_stream).poll_next(cx) {
Poll::Ready(Some(msg)) => msg,
Poll::Ready(None) => return Poll::Ready(None),
Poll::Pending => return Poll::Pending,
};
debug!(target: "validation", "Processing statement for live validation leaf-work");
if let Ok(gmsg) = GossipMessage::decode(&mut &msg.message[..]) {
return Poll::Ready(Some((gmsg, msg.sender)))
}
}
}
}
/// Status of a Polkadot node.
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
pub struct Status {
collating_for: Option<(CollatorId, ParaId)>,
}
struct PoVBlockRequest {
attempted_peers: HashSet<ValidatorId>,
validation_leaf: Hash,
candidate_hash: Hash,
block_data_hash: Hash,
sender: oneshot::Sender<PoVBlock>,
canon_roots: StructuredUnroutedIngress,
}
impl PoVBlockRequest {
// Attempt to process a response. If the provided block is invalid,
// this returns an error result containing the unmodified request.
//
// If `Ok(())` is returned, that indicates that the request has been processed.
fn process_response(self, pov_block: PoVBlock) -> Result<(), Self> {
if pov_block.block_data.hash() != self.block_data_hash {
return Err(self);
}
match polkadot_validation::validate_incoming(&self.canon_roots, &pov_block.ingress) {
Ok(()) => {
let _ = self.sender.send(pov_block);
Ok(())
}
Err(_) => Err(self)
}
}
}
// ensures collator-protocol messages are sent in correct order.
// session key must be sent before collator role.
enum CollatorState {
Fresh,
RolePending(Role),
Primed(Option<Role>),
}
impl CollatorState {
fn send_key<F: FnMut(Message)>(&mut self, key: ValidatorId, mut f: F) {
f(Message::ValidatorId(key));
if let CollatorState::RolePending(role) = *self {
f(Message::CollatorRole(role));
*self = CollatorState::Primed(Some(role));
}
}
fn set_role<F: FnMut(Message)>(&mut self, role: Role, mut f: F) {
if let CollatorState::Primed(ref mut r) = *self {
f(Message::CollatorRole(role));
*r = Some(role);
} else {
*self = CollatorState::RolePending(role);
}
}
fn role(&self) -> Option<Role> {
match *self {
CollatorState::Fresh => None,
CollatorState::RolePending(role) => Some(role),
CollatorState::Primed(role) => role,
}
}
}
struct PeerInfo {
collating_for: Option<(CollatorId, ParaId)>,
validator_keys: RecentValidatorIds,
claimed_validator: bool,
collator_state: CollatorState,
}
impl PeerInfo {
fn should_send_key(&self) -> bool {
self.claimed_validator || self.collating_for.is_some()
}
}
/// Polkadot-specific messages.
#[derive(Debug, Encode, Decode)]
pub enum Message {
/// As a validator, tell the peer your current session key.
// TODO: do this with a cryptographic proof of some kind
// https://github.com/paritytech/polkadot/issues/47
ValidatorId(ValidatorId),
/// Requesting parachain proof-of-validation block (relay_parent, candidate_hash).
RequestPovBlock(RequestId, Hash, Hash),
/// Provide requested proof-of-validation block data by candidate hash or nothing if unknown.
PovBlock(RequestId, Option<PoVBlock>),
/// Tell a collator their role.
CollatorRole(Role),
/// A collation provided by a peer. Relay parent and collation.
Collation(Hash, Collation),
}
fn send_polkadot_message(ctx: &mut dyn Context<Block>, to: PeerId, message: Message) {
trace!(target: "p_net", "Sending polkadot message to {}: {:?}", to, message);
let encoded = message.encode();
ctx.send_chain_specific(to, encoded)
}
/// Polkadot protocol attachment for substrate.
pub struct PolkadotProtocol {
peers: HashMap<PeerId, PeerInfo>,
collating_for: Option<(CollatorId, ParaId)>,
collators: CollatorPool,
validators: HashMap<ValidatorId, PeerId>,
local_collations: LocalCollations<Collation>,
live_validation_leaves: LiveValidationLeaves,
in_flight: HashMap<(RequestId, PeerId), PoVBlockRequest>,
pending: Vec<PoVBlockRequest>,
availability_store: Option<av_store::Store>,
next_req_id: u64,
}
impl PolkadotProtocol {
/// Instantiate a polkadot protocol handler.
pub fn new(collating_for: Option<(CollatorId, ParaId)>) -> Self {
PolkadotProtocol {
peers: HashMap::new(),
collators: CollatorPool::new(),
collating_for,
validators: HashMap::new(),
local_collations: LocalCollations::new(),
live_validation_leaves: LiveValidationLeaves::new(),
in_flight: HashMap::new(),
pending: Vec::new(),
availability_store: None,
next_req_id: 1,
}
}
/// Fetch block data by candidate receipt.
fn fetch_pov_block(
&mut self,
ctx: &mut dyn Context<Block>,
candidate: &CandidateReceipt,
relay_parent: Hash,
canon_roots: StructuredUnroutedIngress,
) -> oneshot::Receiver<PoVBlock> {
let (tx, rx) = oneshot::channel();
self.pending.push(PoVBlockRequest {
attempted_peers: Default::default(),
validation_leaf: relay_parent,
candidate_hash: candidate.hash(),
block_data_hash: candidate.block_data_hash,
sender: tx,
canon_roots,
});
self.dispatch_pending_requests(ctx);
rx
}
/// Note new leaf to do validation work at
fn new_validation_leaf_work(
&mut self,
ctx: &mut dyn Context<Block>,
params: validation::LeafWorkParams,
) -> validation::LiveValidationLeaf {
let (work, new_local) = self.live_validation_leaves
.new_validation_leaf(params);
if let Some(new_local) = new_local {
for (id, peer_data) in self.peers.iter_mut()
.filter(|&(_, ref info)| info.should_send_key())
{
peer_data.collator_state.send_key(new_local.clone(), |msg| send_polkadot_message(
ctx,
id.clone(),
msg
));
}
}
work
}
// true indicates that it was removed actually.
fn remove_validation_session(&mut self, parent_hash: Hash) -> bool {
self.live_validation_leaves.remove(parent_hash)
}
fn dispatch_pending_requests(&mut self, ctx: &mut dyn Context<Block>) {
let mut new_pending = Vec::new();
let validator_keys = &mut self.validators;
let next_req_id = &mut self.next_req_id;
let in_flight = &mut self.in_flight;
for mut pending in ::std::mem::replace(&mut self.pending, Vec::new()) {
let parent = pending.validation_leaf;
let c_hash = pending.candidate_hash;
let still_pending = self.live_validation_leaves.with_pov_block(&parent, &c_hash, |x| match x {
Ok(data @ &_) => {
// answer locally.
let _ = pending.sender.send(data.clone());
None
}
Err(Some(known_keys)) => {
let next_peer = known_keys.iter()
.filter_map(|x| validator_keys.get(x).map(|id| (x.clone(), id.clone())))
.find(|&(ref key, _)| pending.attempted_peers.insert(key.clone()))
.map(|(_, id)| id);
// dispatch to peer
if let Some(who) = next_peer {
let req_id = *next_req_id;
*next_req_id += 1;
send_polkadot_message(
ctx,
who.clone(),
Message::RequestPovBlock(req_id, parent, c_hash),
);
in_flight.insert((req_id, who), pending);
None
} else {
Some(pending)
}
}
Err(None) => None, // no such known validation leaf-work. prune out.
});
if let Some(pending) = still_pending {
new_pending.push(pending);
}
}
self.pending = new_pending;
}
fn on_polkadot_message(&mut self, ctx: &mut dyn Context<Block>, who: PeerId, msg: Message) {
trace!(target: "p_net", "Polkadot message from {}: {:?}", who, msg);
match msg {
Message::ValidatorId(key) => self.on_session_key(ctx, who, key),
Message::RequestPovBlock(req_id, relay_parent, candidate_hash) => {
let pov_block = self.live_validation_leaves.with_pov_block(
&relay_parent,
&candidate_hash,
|res| res.ok().map(|b| b.clone()),
);
send_polkadot_message(ctx, who, Message::PovBlock(req_id, pov_block));
}
Message::PovBlock(req_id, data) => self.on_pov_block(ctx, who, req_id, data),
Message::Collation(relay_parent, collation) => self.on_collation(ctx, who, relay_parent, collation),
Message::CollatorRole(role) => self.on_new_role(ctx, who, role),
}
}
fn on_session_key(&mut self, ctx: &mut dyn Context<Block>, who: PeerId, key: ValidatorId) {
{
let info = match self.peers.get_mut(&who) {
Some(peer) => peer,
None => {
trace!(target: "p_net", "Network inconsistency: message received from unconnected peer {}", who);
return
}
};
if !info.claimed_validator {
ctx.report_peer(who, cost::UNEXPECTED_MESSAGE);
return;
}
ctx.report_peer(who.clone(), benefit::EXPECTED_MESSAGE);
let local_collations = &mut self.local_collations;
let new_collations = match info.validator_keys.insert(key.clone()) {
InsertedRecentKey::AlreadyKnown => Vec::new(),
InsertedRecentKey::New(Some(old_key)) => {
self.validators.remove(&old_key);
local_collations.fresh_key(&old_key, &key)
}
InsertedRecentKey::New(None) => info.collator_state.role()
.map(|r| local_collations.note_validator_role(key.clone(), r))
.unwrap_or_else(Vec::new),
};
for (relay_parent, collation) in new_collations {
send_polkadot_message(
ctx,
who.clone(),
Message::Collation(relay_parent, collation),
)
}
self.validators.insert(key, who);
}
self.dispatch_pending_requests(ctx);
}
fn on_pov_block(
&mut self,
ctx: &mut dyn Context<Block>,
who: PeerId,
req_id: RequestId,
pov_block: Option<PoVBlock>,
) {
match self.in_flight.remove(&(req_id, who.clone())) {
Some(mut req) => {
match pov_block {
Some(pov_block) => {
match req.process_response(pov_block) {
Ok(()) => {
ctx.report_peer(who, benefit::GOOD_POV_BLOCK);
return;
}
Err(r) => {
ctx.report_peer(who, cost::BAD_POV_BLOCK);
req = r;
}
}
},
None => {
ctx.report_peer(who, benefit::EXPECTED_MESSAGE);
}
}
self.pending.push(req);
self.dispatch_pending_requests(ctx);
}
None => ctx.report_peer(who, cost::UNEXPECTED_MESSAGE),
}
}
// when a validator sends us (a collator) a new role.
fn on_new_role(&mut self, ctx: &mut dyn Context<Block>, who: PeerId, role: Role) {
let info = match self.peers.get_mut(&who) {
Some(peer) => peer,
None => {
trace!(target: "p_net", "Network inconsistency: message received from unconnected peer {}", who);
return
}
};
debug!(target: "p_net", "New collator role {:?} from {}", role, who);
if info.validator_keys.as_slice().is_empty() {
ctx.report_peer(who, cost::UNEXPECTED_ROLE)
} else {
// update role for all saved session keys for this validator.
let local_collations = &mut self.local_collations;
for (relay_parent, collation) in info.validator_keys
.as_slice()
.iter()
.cloned()
.flat_map(|k| local_collations.note_validator_role(k, role))
{
debug!(target: "p_net", "Broadcasting collation on relay parent {:?}", relay_parent);
send_polkadot_message(
ctx,
who.clone(),
Message::Collation(relay_parent, collation),
)
}
}
}
/// Convert the given `CollatorId` to a `PeerId`.
pub fn collator_id_to_peer_id(&self, collator_id: &CollatorId) -> Option<&PeerId> {
self.collators.collator_id_to_peer_id(collator_id)
}
}
impl Specialization<Block> for PolkadotProtocol {
fn status(&self) -> Vec<u8> {
Status { collating_for: self.collating_for.clone() }.encode()
}
fn on_connect(&mut self, ctx: &mut dyn Context<Block>, who: PeerId, status: FullStatus) {
let local_status = Status::decode(&mut &status.chain_status[..])
.unwrap_or_else(|_| Status { collating_for: None });
let validator = status.roles.contains(sc_network::config::Roles::AUTHORITY);
let mut peer_info = PeerInfo {
collating_for: local_status.collating_for.clone(),
validator_keys: Default::default(),
claimed_validator: validator,
collator_state: CollatorState::Fresh,
};
if let Some((ref acc_id, ref para_id)) = local_status.collating_for {
if self.collator_peer(acc_id.clone()).is_some() {
ctx.report_peer(who, cost::COLLATOR_ALREADY_KNOWN);
return
}
ctx.report_peer(who.clone(), benefit::NEW_COLLATOR);
let collator_role = self.collators.on_new_collator(
acc_id.clone(),
para_id.clone(),
who.clone(),
);
peer_info.collator_state.set_role(collator_role, |msg| send_polkadot_message(
ctx,
who.clone(),
msg,
));
}
// send session keys.
if peer_info.should_send_key() {
for local_session_key in self.live_validation_leaves.recent_keys() {
peer_info.collator_state.send_key(local_session_key.clone(), |msg| send_polkadot_message(
ctx,
who.clone(),
msg,
));
}
}
self.peers.insert(who, peer_info);
self.dispatch_pending_requests(ctx);
}
fn on_disconnect(&mut self, ctx: &mut dyn Context<Block>, who: PeerId) {
if let Some(info) = self.peers.remove(&who) {
if let Some((acc_id, _)) = info.collating_for {
let new_primary = self.collators.on_disconnect(acc_id)
.and_then(|new_primary| self.collator_peer(new_primary));
if let Some((new_primary, primary_info)) = new_primary {
primary_info.collator_state.set_role(Role::Primary, |msg| send_polkadot_message(
ctx,
new_primary.clone(),
msg,
));
}
}
for key in info.validator_keys.as_slice().iter() {
self.validators.remove(key);
self.local_collations.on_disconnect(key);
}
{
let pending = &mut self.pending;
self.in_flight.retain(|&(_, ref peer), val| {
let retain = peer != &who;
if !retain {
// swap with a dummy value which will be dropped immediately.
let (sender, _) = oneshot::channel();
pending.push(::std::mem::replace(val, PoVBlockRequest {
attempted_peers: Default::default(),
validation_leaf: Default::default(),
candidate_hash: Default::default(),
block_data_hash: Default::default(),
canon_roots: StructuredUnroutedIngress(Vec::new()),
sender,
}));
}
retain
});
}
self.dispatch_pending_requests(ctx);
}
}
fn on_message(
&mut self,
ctx: &mut dyn Context<Block>,
who: PeerId,
message: Vec<u8>,
) {
match Message::decode(&mut &message[..]) {
Ok(msg) => {
ctx.report_peer(who.clone(), benefit::VALID_FORMAT);
self.on_polkadot_message(ctx, who, msg)
},
Err(_) => {
trace!(target: "p_net", "Bad message from {}", who);
ctx.report_peer(who, cost::INVALID_FORMAT);
}
}
}
fn maintain_peers(&mut self, ctx: &mut dyn Context<Block>) {
self.collators.collect_garbage(None);
self.local_collations.collect_garbage(None);
self.dispatch_pending_requests(ctx);
for collator_action in self.collators.maintain_peers() {
match collator_action {
Action::Disconnect(collator) => self.disconnect_bad_collator(ctx, collator),
Action::NewRole(account_id, role) => if let Some((collator, info)) = self.collator_peer(account_id) {
info.collator_state.set_role(role, |msg| send_polkadot_message(
ctx,
collator.clone(),
msg,
))
},
}
}
}
fn on_block_imported(&mut self, _ctx: &mut dyn Context<Block>, hash: Hash, header: &Header) {
self.collators.collect_garbage(Some(&hash));
self.local_collations.collect_garbage(Some(&header.parent_hash));
}
}
impl PolkadotProtocol {
// we received a collation from a peer
fn on_collation(
&mut self,
ctx: &mut dyn Context<Block>,
from: PeerId,
relay_parent: Hash,
collation: Collation
) {
let collation_para = collation.info.parachain_index;
let collated_acc = collation.info.collator.clone();
match self.peers.get(&from) {
None => ctx.report_peer(from, cost::UNKNOWN_PEER),
Some(peer_info) => {
ctx.report_peer(from.clone(), benefit::KNOWN_PEER);
match peer_info.collating_for {
None => ctx.report_peer(from, cost::UNEXPECTED_MESSAGE),
Some((ref acc_id, ref para_id)) => {
ctx.report_peer(from.clone(), benefit::EXPECTED_MESSAGE);
let structurally_valid = para_id == &collation_para && acc_id == &collated_acc;
if structurally_valid && collation.info.check_signature().is_ok() {
debug!(target: "p_net", "Received collation for parachain {:?} from peer {}", para_id, from);
ctx.report_peer(from, benefit::GOOD_COLLATION);
self.collators.on_collation(acc_id.clone(), relay_parent, collation)
} else {
ctx.report_peer(from, cost::INVALID_FORMAT)
};
}
}
},
}
}
fn await_collation(&mut self, relay_parent: Hash, para_id: ParaId) -> oneshot::Receiver<Collation> {
let (tx, rx) = oneshot::channel();
debug!(target: "p_net", "Attempting to get collation for parachain {:?} on relay parent {:?}", para_id, relay_parent);
self.collators.await_collation(relay_parent, para_id, tx);
rx
}
// get connected peer with given account ID for collation.
fn collator_peer(&mut self, collator_id: CollatorId) -> Option<(PeerId, &mut PeerInfo)> {
let check_info = |info: &PeerInfo| info
.collating_for
.as_ref()
.map_or(false, |&(ref acc_id, _)| acc_id == &collator_id);
self.peers
.iter_mut()
.filter(|&(_, ref info)| check_info(&**info))
.map(|(who, info)| (who.clone(), info))
.next()
}
// disconnect a collator by account-id.
fn disconnect_bad_collator(&mut self, ctx: &mut dyn Context<Block>, collator_id: CollatorId) {
if let Some((who, _)) = self.collator_peer(collator_id) {
ctx.report_peer(who, cost::BAD_COLLATION)
}
}
}
impl PolkadotProtocol {
/// Add a local collation and broadcast it to the necessary peers.
///
/// This should be called by a collator intending to get the locally-collated
/// block into the hands of validators.
/// It also places the outgoing message and block data in the local availability store.
pub fn add_local_collation(
&mut self,
ctx: &mut dyn Context<Block>,
relay_parent: Hash,
targets: HashSet<ValidatorId>,
collation: Collation,
outgoing_targeted: OutgoingMessages,
) -> impl Future<Output = ()> {
debug!(target: "p_net", "Importing local collation on relay parent {:?} and parachain {:?}",
relay_parent, collation.info.parachain_index);
for (primary, cloned_collation) in self.local_collations.add_collation(relay_parent, targets, collation.clone()) {
match self.validators.get(&primary) {
Some(who) => {
debug!(target: "p_net", "Sending local collation to {:?}", primary);
send_polkadot_message(
ctx,
who.clone(),
Message::Collation(relay_parent, cloned_collation),
)
},
None =>
warn!(target: "polkadot_network", "Encountered tracked but disconnected validator {:?}", primary),
}
}
let availability_store = self.availability_store.clone();
let collation_cloned = collation.clone();
async move {
if let Some(availability_store) = availability_store {
let _ = availability_store.make_available(av_store::Data {
relay_parent,
parachain_id: collation_cloned.info.parachain_index,
block_data: collation_cloned.pov.block_data.clone(),
outgoing_queues: Some(outgoing_targeted.clone().into()),
}).await;
}
}
}
/// Give the network protocol a handle to an availability store, used for
/// circulation of parachain data required for validation.
pub fn register_availability_store(&mut self, availability_store: ::av_store::Store) {
self.availability_store = Some(availability_store);
}
}
+967
View File
@@ -0,0 +1,967 @@
// Copyright 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.
//! Polkadot-specific base networking protocol.
//!
//! This is implemented using the sc-network APIs for futures-based
//! notifications protocols. In some cases, we emulate request/response on top
//! of the notifications machinery, which is slightly less efficient but not
//! meaningfully so.
//!
//! We handle events from `sc-network` in a thin wrapper that forwards to a
//! background worker which also handles commands from other parts of the node.
use codec::{Decode, Encode};
use futures::channel::{mpsc, oneshot};
use futures::future::Either;
use futures::prelude::*;
use futures::task::{Spawn, SpawnExt};
use log::{debug, trace};
use av_store::Store as AvailabilityStore;
use polkadot_primitives::{
Hash, Block,
parachain::{
PoVBlock, ValidatorId, ValidatorIndex, Collation, CandidateReceipt, OutgoingMessages,
ErasureChunk, ParachainHost, Id as ParaId, CollatorId,
},
};
use polkadot_validation::{
SharedTable, TableRouter, Network as ParachainNetwork, Validated, GenericStatement, Collators,
};
use sc_network::{config::Roles, Event, PeerId};
use sp_api::ProvideRuntimeApi;
use std::collections::HashMap;
use std::pin::Pin;
use std::sync::{Arc, Weak};
use std::time::Duration;
use super::{cost, benefit, PolkadotNetworkService};
use crate::legacy::validation::{RecentValidatorIds, InsertedRecentKey};
use crate::legacy::collator_pool::Role as CollatorRole;
/// The current protocol version.
pub const VERSION: u32 = 1;
/// The minimum supported protocol version.
pub const MIN_SUPPORTED_VERSION: u32 = 1;
/// The engine ID of the polkadot network protocol.
pub const POLKADOT_ENGINE_ID: sp_runtime::ConsensusEngineId = *b"dot2";
pub use crate::legacy::gossip::ChainContext;
// Messages from the service API or network adapter.
enum ServiceToWorkerMsg {
// basic peer messages.
PeerConnected(PeerId, Roles),
PeerMessage(PeerId, Vec<bytes::Bytes>),
PeerDisconnected(PeerId),
// service messages.
BuildConsensusNetworking(Arc<SharedTable>, Vec<ValidatorId>, oneshot::Sender<Router>),
DropConsensusNetworking(Hash),
LocalCollation(
Hash, // relay-parent
Collation,
CandidateReceipt,
(ValidatorIndex, Vec<ErasureChunk>),
),
FetchPoVBlock(
Hash, // relay-parent
CandidateReceipt,
oneshot::Sender<PoVBlock>,
),
AwaitCollation(
Hash, // relay-parent,
ParaId,
oneshot::Sender<Collation>,
),
NoteBadCollator(
CollatorId,
),
}
/// An async handle to the network service.
#[derive(Clone)]
pub struct Service {
sender: mpsc::Sender<ServiceToWorkerMsg>,
network_service: Arc<PolkadotNetworkService>,
}
/// Registers the protocol.
///
/// You are very strongly encouraged to call this method very early on. Any connection open
/// will retain the protocols that were registered then, and not any new one.
pub fn start<C, Api, SP>(
service: Arc<PolkadotNetworkService>,
config: Config,
availability_store: AvailabilityStore,
chain_context: C,
api: Arc<Api>,
executor: SP,
) -> Result<Service, futures::task::SpawnError> where
C: ChainContext + 'static,
Api: ProvideRuntimeApi<Block> + Send + Sync + 'static,
Api::Api: ParachainHost<Block, Error = sp_blockchain::Error>,
SP: Spawn + Clone + Send + 'static,
{
const SERVICE_TO_WORKER_BUF: usize = 256;
let mut event_stream = service.event_stream();
let (mut worker_sender, worker_receiver) = mpsc::channel(SERVICE_TO_WORKER_BUF);
let gossip_validator = crate::legacy::gossip::register_validator(
service.clone(),
chain_context,
&executor,
);
executor.spawn(worker_loop(
config,
service.clone(),
availability_store,
gossip_validator,
worker_sender.clone(),
api,
worker_receiver,
executor.clone(),
))?;
let polkadot_service = Service {
sender: worker_sender.clone(),
network_service: service.clone(),
};
executor.spawn(async move {
while let Some(event) = event_stream.next().await {
let res = match event {
Event::Dht(_) => continue,
Event::NotificationStreamOpened {
remote,
engine_id,
roles,
} => {
if engine_id != POLKADOT_ENGINE_ID { continue }
worker_sender.send(ServiceToWorkerMsg::PeerConnected(remote, roles)).await
},
Event::NotificationStreamClosed {
remote,
engine_id,
} => {
if engine_id != POLKADOT_ENGINE_ID { continue }
worker_sender.send(ServiceToWorkerMsg::PeerDisconnected(remote)).await
},
Event::NotificationsReceived {
remote,
messages,
} => {
let our_notifications = messages.into_iter()
.filter_map(|(engine, message)| if engine == POLKADOT_ENGINE_ID {
Some(message)
} else {
None
})
.collect();
worker_sender.send(
ServiceToWorkerMsg::PeerMessage(remote, our_notifications)
).await
}
};
if let Err(e) = res {
// full is impossible here, as we've `await`ed the value being sent.
if e.is_disconnected() {
break
}
}
}
})?;
Ok(polkadot_service)
}
/// The Polkadot protocol status message.
#[derive(Debug, Encode, Decode)]
pub struct Status {
version: u32, // protocol version.
collating_for: Option<(CollatorId, ParaId)>,
}
/// Polkadot-specific messages from peer to peer.
#[derive(Debug, Encode, Decode)]
pub enum Message {
/// Exchange status with a peer. This should be the first message sent.
#[codec(index = "0")]
Status(Status),
/// Inform a peer of their role as a collator. May only be sent after
/// validator ID.
#[codec(index = "1")]
CollatorRole(CollatorRole),
/// Send a collation.
#[codec(index = "2")]
Collation(Hash, Collation),
/// Inform a peer of a new validator public key.
#[codec(index = "3")]
ValidatorId(ValidatorId),
}
// ensures collator-protocol messages are sent in correct order.
// session key must be sent before collator role.
enum CollatorState {
Fresh,
RolePending(CollatorRole),
Primed(Option<CollatorRole>),
}
impl CollatorState {
fn send_key<F: FnMut(Message)>(&mut self, key: ValidatorId, mut f: F) {
f(Message::ValidatorId(key));
if let CollatorState::RolePending(role) = *self {
f(Message::CollatorRole(role));
*self = CollatorState::Primed(Some(role));
}
}
fn set_role<F: FnMut(Message)>(&mut self, role: CollatorRole, mut f: F) {
if let CollatorState::Primed(ref mut r) = *self {
f(Message::CollatorRole(role));
*r = Some(role);
} else {
*self = CollatorState::RolePending(role);
}
}
}
enum ProtocolState {
Fresh,
Ready(Status, CollatorState),
}
struct PeerData {
claimed_validator: bool,
protocol_state: ProtocolState,
session_keys: RecentValidatorIds,
}
impl PeerData {
fn ready_and_collating_for(&self) -> Option<(CollatorId, ParaId)> {
match self.protocol_state {
ProtocolState::Ready(ref status, _) => status.collating_for.clone(),
_ => None,
}
}
fn collator_state_mut(&mut self) -> Option<&mut CollatorState> {
match self.protocol_state {
ProtocolState::Ready(_, ref mut c_state) => Some(c_state),
_ => None,
}
}
fn should_send_key(&self) -> bool {
self.claimed_validator || self.ready_and_collating_for().is_some()
}
}
struct ConsensusNetworkingInstance {
statement_table: Arc<SharedTable>,
relay_parent: Hash,
attestation_topic: Hash,
_drop_signal: exit_future::Signal,
}
type RegisteredMessageValidator = crate::legacy::gossip::RegisteredMessageValidator<crate::PolkadotProtocol>;
/// Protocol configuration.
#[derive(Default)]
pub struct Config {
/// Which collator-id to use when collating, and on which parachain.
/// `None` if not collating.
pub collating_for: Option<(CollatorId, ParaId)>,
}
struct ProtocolHandler {
service: Arc<PolkadotNetworkService>,
peers: HashMap<PeerId, PeerData>,
collators: crate::legacy::collator_pool::CollatorPool,
local_collations: crate::legacy::local_collations::LocalCollations<Collation>,
config: Config,
}
impl ProtocolHandler {
fn new(
service: Arc<PolkadotNetworkService>,
config: Config,
) -> Self {
ProtocolHandler {
service,
peers: HashMap::new(),
collators: Default::default(),
local_collations: Default::default(),
config,
}
}
fn on_connect(&mut self, peer: PeerId, roles: Roles) {
let claimed_validator = roles.contains(sc_network::config::Roles::AUTHORITY);
self.peers.insert(peer.clone(), PeerData {
claimed_validator,
protocol_state: ProtocolState::Fresh,
session_keys: Default::default(),
});
let status = Message::Status(Status {
version: VERSION,
collating_for: self.config.collating_for.clone(),
}).encode();
self.service.write_notification(peer, POLKADOT_ENGINE_ID, status);
}
fn on_disconnect(&mut self, peer: PeerId) {
let mut new_primary = None;
if let Some(data) = self.peers.remove(&peer) {
if let Some((collator_id, _)) = data.ready_and_collating_for() {
if self.collators.collator_id_to_peer_id(&collator_id) == Some(&peer) {
new_primary = self.collators.on_disconnect(collator_id);
}
}
}
let service = &self.service;
let peers = &mut self.peers;
if let Some(new_primary) = new_primary {
let new_primary_peer_id = match self.collators.collator_id_to_peer_id(&new_primary) {
None => return,
Some(p) => p.clone(),
};
if let Some(c_state) = peers.get_mut(&new_primary_peer_id)
.and_then(|p| p.collator_state_mut())
{
c_state.set_role(
CollatorRole::Primary,
|msg| service.write_notification(
new_primary_peer_id.clone(),
POLKADOT_ENGINE_ID,
msg.encode(),
),
);
}
}
}
fn on_raw_messages(&mut self, remote: PeerId, messages: Vec<bytes::Bytes>) {
for raw_message in messages {
match Message::decode(&mut raw_message.as_ref()) {
Ok(message) => {
self.service.report_peer(remote.clone(), benefit::VALID_FORMAT);
match message {
Message::Status(status) => {
self.on_status(remote.clone(), status);
}
Message::CollatorRole(role) => {
self.on_collator_role(remote.clone(), role)
}
Message::Collation(relay_parent, collation) => {
self.on_remote_collation(remote.clone(), relay_parent, collation);
}
Message::ValidatorId(session_key) => {
self.on_validator_id(remote.clone(), session_key)
}
}
},
Err(_) => self.service.report_peer(remote.clone(), cost::INVALID_FORMAT),
}
}
}
fn on_status(&mut self, remote: PeerId, status: Status) {
let peer = match self.peers.get_mut(&remote) {
None => { self.service.report_peer(remote, cost::UNKNOWN_PEER); return }
Some(p) => p,
};
match peer.protocol_state {
ProtocolState::Fresh => {
peer.protocol_state = ProtocolState::Ready(status, CollatorState::Fresh);
if let Some((collator_id, para_id)) = peer.ready_and_collating_for() {
let collator_attached = self.collators
.collator_id_to_peer_id(&collator_id)
.map_or(false, |id| id != &remote);
// we only care about the first connection from this collator.
if !collator_attached {
let role = self.collators
.on_new_collator(collator_id, para_id, remote.clone());
let service = &self.service;
if let Some(c_state) = peer.collator_state_mut() {
c_state.set_role(role, |msg| service.write_notification(
remote.clone(),
POLKADOT_ENGINE_ID,
msg.encode(),
));
}
}
}
}
ProtocolState::Ready(_, _) => {
self.service.report_peer(remote, cost::UNEXPECTED_MESSAGE);
}
}
}
fn on_remote_collation(&mut self, remote: PeerId, relay_parent: Hash, collation: Collation) {
let peer = match self.peers.get_mut(&remote) {
None => { self.service.report_peer(remote, cost::UNKNOWN_PEER); return }
Some(p) => p,
};
let (collator_id, para_id) = match peer.ready_and_collating_for() {
None => {
self.service.report_peer(remote, cost::UNEXPECTED_MESSAGE);
return
}
Some(x) => x,
};
let collation_para = collation.info.parachain_index;
let collated_acc = collation.info.collator.clone();
let structurally_valid = para_id == collation_para && collator_id == collated_acc;
if structurally_valid && collation.info.check_signature().is_ok() {
debug!(target: "p_net", "Received collation for parachain {:?} from peer {}",
para_id, remote);
if self.collators.collator_id_to_peer_id(&collator_id) == Some(&remote) {
self.collators.on_collation(collator_id, relay_parent, collation);
self.service.report_peer(remote, benefit::GOOD_COLLATION);
}
} else {
self.service.report_peer(remote, cost::INVALID_FORMAT);
}
}
fn on_collator_role(&mut self, remote: PeerId, role: CollatorRole) {
let collations_to_send;
{
let peer = match self.peers.get_mut(&remote) {
None => { self.service.report_peer(remote, cost::UNKNOWN_PEER); return }
Some(p) => p,
};
match peer.protocol_state {
ProtocolState::Fresh => {
self.service.report_peer(remote, cost::UNEXPECTED_MESSAGE);
return;
}
ProtocolState::Ready(_, _) => {
let last_key = match peer.session_keys.as_slice().last() {
None => {
self.service.report_peer(remote, cost::UNEXPECTED_MESSAGE);
return;
}
Some(k) => k,
};
collations_to_send = self.local_collations
.note_validator_role(last_key.clone(), role);
}
}
}
self.send_peer_collations(remote, collations_to_send);
}
fn on_validator_id(&mut self, remote: PeerId, key: ValidatorId) {
let mut collations_to_send = Vec::new();
{
let peer = match self.peers.get_mut(&remote) {
None => { self.service.report_peer(remote, cost::UNKNOWN_PEER); return }
Some(p) => p,
};
match peer.protocol_state {
ProtocolState::Fresh => {
self.service.report_peer(remote, cost::UNEXPECTED_MESSAGE);
return
}
ProtocolState::Ready(_, _) => {
if let InsertedRecentKey::New(Some(last)) = peer.session_keys.insert(key.clone()) {
collations_to_send = self.local_collations.fresh_key(&last, &key);
}
}
}
}
self.send_peer_collations(remote, collations_to_send);
}
fn send_peer_collations(&self, remote: PeerId, collations: Vec<(Hash, Collation)>) {
for (relay_parent, collation) in collations {
self.service.write_notification(
remote.clone(),
POLKADOT_ENGINE_ID,
Message::Collation(relay_parent, collation).encode(),
);
}
}
fn await_collation(
&mut self,
relay_parent: Hash,
para_id: ParaId,
sender: oneshot::Sender<Collation>,
) {
self.collators.await_collation(relay_parent, para_id, sender);
}
fn collect_garbage(&mut self) {
self.collators.collect_garbage(None);
self.local_collations.collect_garbage(None);
}
fn note_bad_collator(&mut self, who: CollatorId) {
if let Some(peer) = self.collators.collator_id_to_peer_id(&who) {
self.service.report_peer(peer.clone(), cost::BAD_COLLATION);
}
}
// distribute a new session key to any relevant peers.
fn distribute_new_session_key(&mut self, key: ValidatorId) {
let service = &self.service;
for (peer_id, peer) in self.peers.iter_mut() {
if !peer.should_send_key() { continue }
if let Some(c_state) = peer.collator_state_mut() {
c_state.send_key(key.clone(), |msg| service.write_notification(
peer_id.clone(),
POLKADOT_ENGINE_ID,
msg.encode(),
));
}
}
}
}
async fn worker_loop<Api, Sp>(
config: Config,
service: Arc<PolkadotNetworkService>,
availability_store: AvailabilityStore,
gossip_handle: RegisteredMessageValidator,
sender: mpsc::Sender<ServiceToWorkerMsg>,
api: Arc<Api>,
mut receiver: mpsc::Receiver<ServiceToWorkerMsg>,
executor: Sp,
) where
Api: ProvideRuntimeApi<Block> + Send + Sync + 'static,
Api::Api: ParachainHost<Block, Error = sp_blockchain::Error>,
Sp: Spawn + Clone + Send + 'static,
{
const COLLECT_GARBAGE_INTERVAL: Duration = Duration::from_secs(29);
let mut protocol_handler = ProtocolHandler::new(service, config);
let mut consensus_instances = HashMap::new();
let mut local_keys = RecentValidatorIds::default();
let mut collect_garbage = stream::unfold((), move |_| {
futures_timer::Delay::new(COLLECT_GARBAGE_INTERVAL).map(|_| Some(((), ())))
}).map(drop);
loop {
let message = match future::select(receiver.next(), collect_garbage.next()).await {
Either::Left((None, _)) | Either::Right((None, _)) => break,
Either::Left((Some(message), _)) => message,
Either::Right(_) => {
protocol_handler.collect_garbage();
continue
}
};
match message {
ServiceToWorkerMsg::PeerConnected(remote, roles) => {
protocol_handler.on_connect(remote, roles);
}
ServiceToWorkerMsg::PeerDisconnected(remote) => {
protocol_handler.on_disconnect(remote);
}
ServiceToWorkerMsg::PeerMessage(remote, messages) => {
protocol_handler.on_raw_messages(remote, messages)
}
ServiceToWorkerMsg::BuildConsensusNetworking(table, authorities, router_sender) => {
// glue: let gossip know about our new local leaf.
let relay_parent = table.consensus_parent_hash().clone();
let (signal, exit) = exit_future::signal();
let router = Router {
inner: Arc::new(RouterInner { relay_parent, sender: sender.clone() }),
};
let key = table.session_key();
if let Some(key) = key {
if let InsertedRecentKey::New(_) = local_keys.insert(key.clone()) {
protocol_handler.distribute_new_session_key(key);
}
}
let new_leaf_actions = gossip_handle.new_local_leaf(
relay_parent,
crate::legacy::gossip::MessageValidationData { authorities },
|queue_root| availability_store.queue_by_root(queue_root),
);
new_leaf_actions.perform(&gossip_handle);
consensus_instances.insert(relay_parent, ConsensusNetworkingInstance {
statement_table: table.clone(),
relay_parent,
attestation_topic: crate::legacy::router::attestation_topic(relay_parent),
_drop_signal: signal,
});
let weak_router = Arc::downgrade(&router.inner);
// glue the incoming messages, shared table, and validation
// work together.
let _ = executor.spawn(statement_import_loop(
relay_parent,
table,
api.clone(),
weak_router,
gossip_handle.clone(),
exit,
executor.clone(),
));
let _ = router_sender.send(router);
}
ServiceToWorkerMsg::DropConsensusNetworking(relay_parent) => {
consensus_instances.remove(&relay_parent);
}
ServiceToWorkerMsg::LocalCollation(relay_parent, collation, receipt, chunks) => {
let instance = match consensus_instances.get(&relay_parent) {
None => continue,
Some(instance) => instance,
};
distribute_local_collation(
instance,
collation,
receipt,
chunks,
&gossip_handle,
);
}
ServiceToWorkerMsg::FetchPoVBlock(_relay_parent, _candidate, _sender) => {
// TODO https://github.com/paritytech/polkadot/issues/742:
// create a filter on gossip for it and send to sender.
}
ServiceToWorkerMsg::AwaitCollation(relay_parent, para_id, sender) => {
debug!(target: "p_net", "Attempting to get collation for parachain {:?} on relay parent {:?}", para_id, relay_parent);
protocol_handler.await_collation(relay_parent, para_id, sender)
}
ServiceToWorkerMsg::NoteBadCollator(collator) => {
protocol_handler.note_bad_collator(collator);
}
}
}
}
// the internal loop of waiting for messages and spawning validation work
// as a result of those messages. this future exits when `exit` is ready.
async fn statement_import_loop<Api>(
relay_parent: Hash,
table: Arc<SharedTable>,
api: Arc<Api>,
weak_router: Weak<RouterInner>,
validator: RegisteredMessageValidator,
mut exit: exit_future::Exit,
executor: impl Spawn,
) where
Api: ProvideRuntimeApi<Block> + Send + Sync + 'static,
Api::Api: ParachainHost<Block, Error = sp_blockchain::Error>,
{
let topic = crate::legacy::router::attestation_topic(relay_parent);
let mut checked_messages = validator.gossip_messages_for(topic)
.filter_map(|msg| match msg.0 {
crate::legacy::gossip::GossipMessage::Statement(s) => future::ready(Some(s.signed_statement)),
_ => future::ready(None),
});
let mut deferred_statements = crate::legacy::router::DeferredStatements::new();
loop {
let statement = match future::select(exit, checked_messages.next()).await {
Either::Left(_) | Either::Right((None, _)) => return,
Either::Right((Some(statement), e)) => {
exit = e;
statement
}
};
// defer any statements for which we haven't imported the candidate yet
let c_hash = {
let candidate_data = match statement.statement {
GenericStatement::Candidate(ref c) => Some(c.hash()),
GenericStatement::Valid(ref hash)
| GenericStatement::Invalid(ref hash)
=> table.with_candidate(hash, |c| c.map(|_| *hash)),
};
match candidate_data {
Some(x) => x,
None => {
deferred_statements.push(statement);
continue;
}
}
};
// import all statements pending on this candidate
let (mut statements, _traces) = if let GenericStatement::Candidate(_) = statement.statement {
deferred_statements.take_deferred(&c_hash)
} else {
(Vec::new(), Vec::new())
};
// prepend the candidate statement.
debug!(target: "validation", "Importing statements about candidate {:?}", c_hash);
statements.insert(0, statement);
let producers: Vec<_> = {
// create a temporary router handle for importing all of these statements
let temp_router = match weak_router.upgrade() {
None => break,
Some(inner) => Router { inner },
};
table.import_remote_statements(
&temp_router,
statements.iter().cloned(),
)
};
// dispatch future work as necessary.
for (producer, statement) in producers.into_iter().zip(statements) {
if let Some(_sender) = table.index_to_id(statement.sender) {
if let Some(producer) = producer {
trace!(target: "validation", "driving statement work to completion");
let work = producer.prime(api.clone()).validate();
let work = future::select(work.boxed(), exit.clone()).map(drop);
let _ = executor.spawn(work);
}
}
}
}
}
// distribute a "local collation": this is the collation gotten by a validator
// from a collator. it needs to be distributed to other validators in the same
// group.
fn distribute_local_collation(
instance: &ConsensusNetworkingInstance,
collation: Collation,
receipt: CandidateReceipt,
chunks: (ValidatorIndex, Vec<ErasureChunk>),
gossip_handle: &RegisteredMessageValidator,
) {
// produce a signed statement.
let hash = receipt.hash();
let erasure_root = receipt.erasure_root;
let validated = Validated::collated_local(
receipt,
collation.pov.clone(),
OutgoingMessages { outgoing_messages: Vec::new() },
);
let statement = crate::legacy::gossip::GossipStatement::new(
instance.relay_parent,
match instance.statement_table.import_validated(validated) {
None => return,
Some(s) => s,
}
);
gossip_handle.gossip_message(instance.attestation_topic, statement.into());
for chunk in chunks.1 {
let index = chunk.index;
let message = crate::legacy::gossip::ErasureChunkMessage {
chunk,
relay_parent: instance.relay_parent,
candidate_hash: hash,
};
gossip_handle.gossip_message(
av_store::erasure_coding_topic(instance.relay_parent, erasure_root, index),
message.into(),
);
}
}
/// Routing logic for a particular attestation session.
#[derive(Clone)]
pub struct Router {
inner: Arc<RouterInner>,
}
// note: do _not_ make this `Clone`: the drop implementation needs to _uniquely_
// send the `DropConsensusNetworking` message.
struct RouterInner {
relay_parent: Hash,
sender: mpsc::Sender<ServiceToWorkerMsg>,
}
impl Drop for RouterInner {
fn drop(&mut self) {
let res = self.sender.try_send(
ServiceToWorkerMsg::DropConsensusNetworking(self.relay_parent)
);
if let Err(e) = res {
assert!(
!e.is_full(),
"futures 0.3 guarantees at least one free slot in the capacity \
per sender; this is the first message sent via this sender; \
therefore we will not have to wait for capacity; qed"
);
// other error variants (disconnection) are fine here.
}
}
}
impl ParachainNetwork for Service {
type Error = future::Either<mpsc::SendError, oneshot::Canceled>;
type TableRouter = Router;
type BuildTableRouter = Pin<Box<dyn Future<Output=Result<Router,Self::Error>>>>;
fn build_table_router(
&self,
table: Arc<SharedTable>,
authorities: &[ValidatorId],
) -> Self::BuildTableRouter {
let authorities = authorities.to_vec();
let mut sender = self.sender.clone();
let (tx, rx) = oneshot::channel();
Box::pin(async move {
sender.send(
ServiceToWorkerMsg::BuildConsensusNetworking(table, authorities, tx)
).map_err(future::Either::Left).await?;
rx.map_err(future::Either::Right).await
})
}
}
impl Collators for Service {
type Error = future::Either<mpsc::SendError, oneshot::Canceled>;
type Collation = Pin<Box<dyn Future<Output = Result<Collation, Self::Error>> + Send>>;
fn collate(&self, parachain: ParaId, relay_parent: Hash) -> Self::Collation {
let (tx, rx) = oneshot::channel();
let mut sender = self.sender.clone();
Box::pin(async move {
sender.send(
ServiceToWorkerMsg::AwaitCollation(relay_parent, parachain, tx)
).map_err(future::Either::Left).await?;
rx.map_err(future::Either::Right).await
})
}
fn note_bad_collator(&self, collator: CollatorId) {
let _ = self.sender.clone().try_send(ServiceToWorkerMsg::NoteBadCollator(collator));
}
}
/// Errors when interacting with the statement router.
#[derive(Debug, derive_more::Display, derive_more::From)]
pub enum RouterError {
#[display(fmt = "Encountered unexpected I/O error: {}", _0)]
Io(std::io::Error),
#[display(fmt = "Worker hung up while answering request.")]
Canceled(oneshot::Canceled),
#[display(fmt = "Could not reach worker with request: {}", _0)]
SendError(mpsc::SendError),
}
impl TableRouter for Router {
type Error = RouterError;
type SendLocalCollation = Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send>>;
type FetchValidationProof = Pin<Box<dyn Future<Output = Result<PoVBlock, Self::Error>> + Send>>;
fn local_collation(
&self,
collation: Collation,
receipt: CandidateReceipt,
_outgoing: OutgoingMessages,
chunks: (ValidatorIndex, &[ErasureChunk]),
) -> Self::SendLocalCollation {
let message = ServiceToWorkerMsg::LocalCollation(
self.inner.relay_parent.clone(),
collation,
receipt,
(chunks.0, chunks.1.to_vec()),
);
let mut sender = self.inner.sender.clone();
Box::pin(async move {
sender.send(message).map_err(Into::into).await
})
}
fn fetch_pov_block(&self, candidate: &CandidateReceipt) -> Self::FetchValidationProof {
let (tx, rx) = oneshot::channel();
let message = ServiceToWorkerMsg::FetchPoVBlock(
self.inner.relay_parent.clone(),
candidate.clone(),
tx,
);
let mut sender = self.inner.sender.clone();
Box::pin(async move {
sender.send(message).await?;
rx.map_err(Into::into).await
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn router_inner_drop_sends_worker_message() {
let parent = [1; 32].into();
let (sender, mut receiver) = mpsc::channel(0);
drop(RouterInner {
relay_parent: parent,
sender,
});
match receiver.try_next() {
Ok(Some(ServiceToWorkerMsg::DropConsensusNetworking(x))) => assert_eq!(parent, x),
_ => panic!("message not sent"),
}
}
}
+7 -3
View File
@@ -22,7 +22,10 @@ use sc_client::LongestChain;
use std::sync::Arc;
use std::time::Duration;
use polkadot_primitives::{parachain, Hash, BlockId, AccountId, Nonce, Balance};
use polkadot_network::{gossip::{self as network_gossip, Known}, validation::ValidationNetwork};
use polkadot_network::legacy::{
gossip::{self as network_gossip, Known},
validation::ValidationNetwork,
};
use service::{error::{Error as ServiceError}, ServiceBuilder};
use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider};
use inherents::InherentDataProviders;
@@ -39,7 +42,7 @@ pub use sc_client_api::backend::Backend;
pub use sp_api::{Core as CoreApi, ConstructRuntimeApi, ProvideRuntimeApi, StateBackend};
pub use sp_runtime::traits::HasherFor;
pub use consensus_common::SelectChain;
pub use polkadot_network::PolkadotProtocol;
pub use polkadot_network::legacy::PolkadotProtocol;
pub use polkadot_primitives::parachain::{CollatorId, ParachainHost};
pub use polkadot_primitives::Block;
pub use sp_core::Blake2Hasher;
@@ -342,7 +345,8 @@ pub fn new_full<Runtime, Dispatch, Extrinsic>(
let mut path = PathBuf::from(db_path);
path.push("availability");
let gossip = polkadot_network::AvailabilityNetworkShim(gossip_validator.clone());
let gossip = polkadot_network::legacy
::AvailabilityNetworkShim(gossip_validator.clone());
#[cfg(not(target_os = "unknown"))]
{
+1 -1
View File
@@ -132,7 +132,7 @@ pub struct Proposer<Client, TxPool, Backend> {
parent_hash: Hash,
parent_id: BlockId,
parent_number: BlockNumber,
tracker: Arc<crate::validation_service::ValidationInstanceHandle>,
tracker: crate::validation_service::ValidationInstanceHandle,
transaction_pool: Arc<TxPool>,
slot_duration: u64,
backend: Arc<Backend>,
+3
View File
@@ -55,6 +55,9 @@ pub trait Collators: Clone {
///
/// This does not have to guarantee local availability, as a valid collation
/// will be passed to the `TableRouter` instance.
///
/// The returned future may be prematurely concluded if the `relay_parent` goes
/// out of date.
fn collate(&self, parachain: ParaId, relay_parent: Hash) -> Self::Collation;
/// Note a bad collator. TODO: take proof (https://github.com/paritytech/polkadot/issues/217)
+9 -3
View File
@@ -76,9 +76,13 @@ 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`.
/// Once all instances are dropped, consensus networking for this router
/// should be cleaned up.
pub trait TableRouter: Clone {
/// Errors when fetching data from the network.
type Error: std::fmt::Debug;
/// Future that drives sending of the local collation to the network.
type SendLocalCollation: Future<Output=Result<(), Self::Error>>;
/// Future that resolves when candidate data is fetched.
type FetchValidationProof: Future<Output=Result<PoVBlock, Self::Error>>;
@@ -90,9 +94,12 @@ pub trait TableRouter: Clone {
receipt: CandidateReceipt,
outgoing: OutgoingMessages,
chunks: (ValidatorIndex, &[ErasureChunk]),
);
) -> Self::SendLocalCollation;
/// Fetch validation proof for a specific candidate.
///
/// This future must conclude once all `Clone`s of this `TableRouter` have
/// been cleaned up.
fn fetch_pov_block(&self, candidate: &CandidateReceipt) -> Self::FetchValidationProof;
}
@@ -111,11 +118,10 @@ pub trait Network {
/// Instantiate a table router using the given shared table.
/// Also pass through any outgoing messages to be broadcast to peers.
fn communication_for(
fn build_table_router(
&self,
table: Arc<SharedTable>,
authorities: &[ValidatorId],
exit: exit_future::Exit,
) -> Self::BuildTableRouter;
}
+2 -1
View File
@@ -620,6 +620,7 @@ mod tests {
struct DummyRouter;
impl TableRouter for DummyRouter {
type Error = ::std::io::Error;
type SendLocalCollation = future::Ready<Result<(),Self::Error>>;
type FetchValidationProof = future::Ready<Result<PoVBlock,Self::Error>>;
fn local_collation(
@@ -628,7 +629,7 @@ mod tests {
_candidate: CandidateReceipt,
_outgoing: OutgoingMessages,
_chunks: (ValidatorIndex, &[ErasureChunk])
) {}
) -> Self::SendLocalCollation { future::ready(Ok(())) }
fn fetch_pov_block(&self, _candidate: &CandidateReceipt) -> Self::FetchValidationProof {
future::ok(pov_block_with_data(vec![1, 2, 3, 4, 5]))
@@ -34,7 +34,7 @@ use sp_blockchain::HeaderBackend;
use block_builder::BlockBuilderApi;
use consensus::SelectChain;
use futures::prelude::*;
use futures::{future::select, task::{Spawn, SpawnExt}};
use futures::task::{Spawn, SpawnExt};
use polkadot_primitives::{Block, Hash, BlockId};
use polkadot_primitives::parachain::{
Chain, ParachainHost, Id as ParaId, ValidatorIndex, ValidatorId, ValidatorPair,
@@ -57,14 +57,14 @@ pub type TaskExecutor = Arc<dyn Spawn + Send + Sync>;
// They send a oneshot channel.
type ValidationInstanceRequest = (
Hash,
futures::channel::oneshot::Sender<Result<Arc<ValidationInstanceHandle>, Error>>,
futures::channel::oneshot::Sender<Result<ValidationInstanceHandle, Error>>,
);
/// A handle to a single instance of parachain validation, which is pinned to
/// a specific relay-chain block. This is the instance that should be used when
/// constructing any
#[derive(Clone)]
pub(crate) struct ValidationInstanceHandle {
_drop_signal: exit_future::Signal,
table: Arc<SharedTable>,
started: Instant,
}
@@ -92,7 +92,7 @@ impl ServiceHandle {
///
/// This can fail if the service task has shut down for some reason.
pub(crate) async fn get_validation_instance(self, relay_parent: Hash)
-> Result<Arc<ValidationInstanceHandle>, Error>
-> Result<ValidationInstanceHandle, Error>
{
let mut sender = self.sender;
let instance_rx = loop {
@@ -261,7 +261,7 @@ pub(crate) struct ParachainValidationInstances<C, N, P, SP> {
availability_store: AvailabilityStore,
/// Live agreements. Maps relay chain parent hashes to attestation
/// instances.
live_instances: HashMap<Hash, Arc<ValidationInstanceHandle>>,
live_instances: HashMap<Hash, ValidationInstanceHandle>,
}
impl<C, N, P, SP> ParachainValidationInstances<C, N, P, SP> where
@@ -289,7 +289,7 @@ impl<C, N, P, SP> ParachainValidationInstances<C, N, P, SP> where
keystore: &KeyStorePtr,
max_block_data_size: Option<u64>,
)
-> Result<Arc<ValidationInstanceHandle>, Error>
-> Result<ValidationInstanceHandle, Error>
{
use primitives::Pair;
@@ -344,23 +344,19 @@ impl<C, N, P, SP> ParachainValidationInstances<C, N, P, SP> where
max_block_data_size,
));
let (_drop_signal, exit) = exit_future::signal();
let router = self.network.communication_for(
let router = self.network.build_table_router(
table.clone(),
&validators,
exit.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);
self.launch_work(parent_hash, id, router, max_block_data_size, validators.len(), index);
}
let tracker = Arc::new(ValidationInstanceHandle {
let tracker = ValidationInstanceHandle {
table,
started: Instant::now(),
_drop_signal,
});
};
self.live_instances.insert(parent_hash, tracker.clone());
@@ -381,7 +377,6 @@ impl<C, N, P, SP> ParachainValidationInstances<C, N, P, SP> where
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();
@@ -423,13 +418,20 @@ impl<C, N, P, SP> ParachainValidationInstances<C, N, P, SP> where
"Failed to add erasure chunks: {}", e
);
} else {
router.local_collation(
let res = router.local_collation(
collation,
receipt,
outgoing_targeted,
(local_id, &chunks),
).await;
if let Err(e) = res {
warn!(
target: "validation",
"Failed to notify network of local collation: {:?}", e
);
}
};
}
Err(e) => {
warn!(target: "validation", "Failed to produce a receipt: {:?}", e);
@@ -442,17 +444,17 @@ impl<C, N, P, SP> ParachainValidationInstances<C, N, P, SP> where
}
};
let router = build_router
let router_work = build_router
.map_ok(with_router)
.map_err(|e| {
warn!(target: "validation" , "Failed to build table router: {:?}", e);
});
})
.map(|_| ());
let cancellable_work = select(exit, router).map(drop);
// spawn onto thread pool.
if self.spawner.spawn(cancellable_work).is_err() {
error!("Failed to spawn cancellable work task");
if self.spawner.spawn(router_work).is_err() {
error!("Failed to spawn router work task");
}
}
}