Initial version of the polite-grandpa networking protocol (#2110)

* Consensus status packet

* Allow for repropagation after status

* More generic gossip

* add a basic view struct and gossip module

* move gossip stuff to the gossip module

* integrate view into gossip

* some reshuffling

* alter rules for keeping one commit at a time in view

* Allow sending addressed messages

* don't cast outgoing votes if we know that we voted before

* Handle one hop messages

* initial run at polite grandpa

* build WASM

* handle neighbor messages

* refactor validator's internals into an Inner struct

* gossip only knows to keep or discard messages. optimize should_send_to

* Periodic rebroadcast

* implement `should_send_to` and message_expired

* track peers' best received commit height

* Pass peer id to topic steam

* kill rebroadcasting network

* Notify about existing peers

* clean up network APIs a bunch

* implement gossip::send_message for direct messages

* refactor network trait

* implement gossip::send_message for direct messages

* get all non set-change tests passing

* treat unknown rebroadcasts as broadcasts

* get all other main tests passing

* remove unimplemented test

* everything compiles

* treat unknown rebroadcasts as broadcasts

* Rebradcast interval

* Apply suggestions from code review

Style

Co-Authored-By: arkpar <arkady.paronyan@gmail.com>

* Style

* some module docs

* address some grumbles + docs

* allow rebroadcast every few minutes

* send_topic && generic context

* some tests for view change

* more grumbles & tests

* use send_peer
This commit is contained in:
Robert Habermeier
2019-04-02 18:09:05 +02:00
committed by GitHub
parent b7eeb28de9
commit bb95e7d6a2
12 changed files with 2042 additions and 1287 deletions
@@ -1,543 +0,0 @@
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Incoming message streams that verify signatures, and outgoing message streams
//! that sign or re-shape.
use std::collections::HashMap;
use std::sync::Arc;
use grandpa::VoterSet;
use grandpa::Message::{Prevote, Precommit};
use futures::prelude::*;
use futures::sync::mpsc;
use log::{debug, trace};
use parity_codec::{Encode, Decode};
use substrate_primitives::{ed25519, Pair};
use substrate_telemetry::{telemetry, CONSENSUS_INFO};
use runtime_primitives::traits::Block as BlockT;
use tokio::timer::Interval;
use crate::{Error, Network, Message, SignedMessage, Commit,
CompactCommit, GossipMessage, FullCommitMessage, VoteOrPrecommitMessage};
use ed25519::{Public as AuthorityId, Signature as AuthoritySignature};
fn localized_payload<E: Encode>(round: u64, set_id: u64, message: &E) -> Vec<u8> {
(message, round, set_id).encode()
}
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
struct Round(u64);
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
struct SetId(u64);
enum Broadcast<Block: BlockT> {
// round, set id, encoded commit.
Commit(Round, SetId, Vec<u8>),
// round, set id, encoded signed message.
Message(Round, SetId, Vec<u8>),
// round, set id, announcement of block hash that should be downloaded
Announcement(Round, SetId, Block::Hash),
// round, set id being dropped.
DropRound(Round, SetId),
// set_id being dropped.
DropSet(SetId),
}
impl<Block: BlockT> Broadcast<Block> {
fn set_id(&self) -> SetId {
match *self {
Broadcast::Commit(_, s, _) => s,
Broadcast::Message(_, s, _) => s,
Broadcast::Announcement(_, s, _) => s,
Broadcast::DropRound(_, s) => s,
Broadcast::DropSet(s) => s,
}
}
}
/// Produces a future that should be run in the background and proxies
/// and rebroadcasts messages.
pub(crate) fn rebroadcasting_network<B: BlockT, N: Network<B>>(network: N) -> (BroadcastWorker<B, N>, BroadcastHandle<B, N>) {
use std::time::Duration;
const REBROADCAST_PERIOD: Duration = Duration::from_secs(60);
let (tx, rx) = mpsc::unbounded();
(
BroadcastWorker {
interval: Interval::new_interval(REBROADCAST_PERIOD),
set_id: SetId(0), // will be overwritten on first item to broadcast.
last_commit: None,
round_messages: (Round(0), Vec::new()),
announcements: HashMap::new(),
network: network.clone(),
incoming_broadcast: rx,
},
BroadcastHandle {
relay: tx,
network,
},
)
}
// A worker which broadcasts messages to the background, potentially
// rebroadcasting.
#[must_use = "network rebroadcast future must be driven to completion"]
pub(crate) struct BroadcastWorker<B: BlockT, N: Network<B>> {
interval: Interval,
set_id: SetId,
last_commit: Option<(Round, Vec<u8>)>,
round_messages: (Round, Vec<Vec<u8>>),
announcements: HashMap<B::Hash, Round>,
network: N,
incoming_broadcast: mpsc::UnboundedReceiver<Broadcast<B>>,
}
/// A handle used by communication work to broadcast to network.
#[derive(Clone)]
pub(crate) struct BroadcastHandle<B: BlockT, N> {
relay: mpsc::UnboundedSender<Broadcast<B>>,
network: N,
}
impl<B: BlockT, N: Network<B>> Future for BroadcastWorker<B, N> {
type Item = ();
type Error = Error;
fn poll(&mut self) -> Poll<(), Error> {
{
let mut rebroadcast = false;
loop {
match self.interval.poll().map_err(Error::Timer)? {
Async::NotReady => break,
Async::Ready(_) => { rebroadcast = true; }
}
}
if rebroadcast {
let SetId(set_id) = self.set_id;
if let Some((Round(c_round), ref c_commit)) = self.last_commit {
self.network.send_commit(c_round, set_id, c_commit.clone(), true);
}
let Round(round) = self.round_messages.0;
for message in self.round_messages.1.iter().cloned() {
self.network.send_message(round, set_id, message, true);
}
for (&announce_hash, &Round(round)) in &self.announcements {
self.network.announce(round, set_id, announce_hash);
}
}
}
loop {
match self.incoming_broadcast.poll().expect("UnboundedReceiver does not yield errors; qed") {
Async::NotReady => return Ok(Async::NotReady),
Async::Ready(None) => return Err(Error::Network(
"all broadcast handles dropped, connection to network severed".into()
)),
Async::Ready(Some(item)) => {
if item.set_id() > self.set_id {
self.set_id = item.set_id();
self.last_commit = None;
self.round_messages = (Round(0), Vec::new());
self.announcements.clear();
}
match item {
Broadcast::Commit(round, set_id, commit) => {
if self.set_id == set_id {
if round >= self.last_commit.as_ref()
.map_or(Round(0), |&(r, _)| r)
{
self.last_commit = Some((round, commit.clone()));
}
}
// always send out to network.
self.network.send_commit(round.0, self.set_id.0, commit, false);
}
Broadcast::Message(round, set_id, message) => {
if self.set_id == set_id {
if round > self.round_messages.0 {
self.round_messages = (round, vec![message.clone()]);
} else if round == self.round_messages.0 {
self.round_messages.1.push(message.clone());
};
// ignore messages from earlier rounds.
}
// always send out to network.
self.network.send_message(round.0, set_id.0, message, false);
}
Broadcast::Announcement(round, set_id, hash) => {
if self.set_id == set_id {
self.announcements.insert(hash, round);
}
// always send out.
self.network.announce(round.0, set_id.0, hash);
}
Broadcast::DropRound(round, set_id) => {
// stop making announcements for any dead rounds.
self.announcements.retain(|_, &mut r| r > round);
self.network.drop_round_messages(round.0, set_id.0);
}
Broadcast::DropSet(set_id) => {
// stop making announcements for any dead rounds.
self.network.drop_set_messages(set_id.0);
}
}
}
}
}
}
}
impl<B: BlockT, N: Network<B>> Network<B> for BroadcastHandle<B, N> {
type In = N::In;
fn messages_for(&self, round: u64, set_id: u64) -> Self::In {
self.network.messages_for(round, set_id)
}
fn send_message(&self, round: u64, set_id: u64, message: Vec<u8>, _force: bool) {
let _ = self.relay.unbounded_send(Broadcast::Message(Round(round), SetId(set_id), message));
}
fn drop_round_messages(&self, round: u64, set_id: u64) {
let _ = self.relay.unbounded_send(Broadcast::DropRound(Round(round), SetId(set_id)));
}
fn drop_set_messages(&self, set_id: u64) {
let _ = self.relay.unbounded_send(Broadcast::DropSet(SetId(set_id)));
}
fn commit_messages(&self, set_id: u64) -> Self::In {
self.network.commit_messages(set_id)
}
fn send_commit(&self, round: u64, set_id: u64, message: Vec<u8>, _force: bool) {
let _ = self.relay.unbounded_send(Broadcast::Commit(Round(round), SetId(set_id), message));
}
fn announce(&self, round: u64, set_id: u64, block: B::Hash) {
let _ = self.relay.unbounded_send(
Broadcast::Announcement(Round(round), SetId(set_id), block)
);
}
}
// check a message.
pub(crate) fn check_message_sig<Block: BlockT>(
message: &Message<Block>,
id: &AuthorityId,
signature: &AuthoritySignature,
round: u64,
set_id: u64,
) -> Result<(), ()> {
let as_public = AuthorityId::from_raw(id.0);
let encoded_raw = localized_payload(round, set_id, message);
if ed25519::Pair::verify(signature, &encoded_raw, as_public) {
Ok(())
} else {
debug!(target: "afg", "Bad signature on message from {:?}", id);
Err(())
}
}
/// converts a message stream into a stream of signed messages.
/// the output stream checks signatures also.
pub(crate) fn checked_message_stream<Block: BlockT, S>(
inner: S,
voters: Arc<VoterSet<AuthorityId>>,
)
-> impl Stream<Item=SignedMessage<Block>,Error=Error> where
S: Stream<Item=Vec<u8>,Error=()>
{
inner
.filter_map(|raw| {
let decoded = GossipMessage::<Block>::decode(&mut &raw[..]);
if decoded.is_none() {
debug!(target: "afg", "Skipping malformed message {:?}", raw);
}
decoded
})
.and_then(move |msg| {
match msg {
GossipMessage::VoteOrPrecommit(msg) => {
// check signature.
if !voters.contains_key(&msg.message.id) {
debug!(target: "afg", "Skipping message from unknown voter {}", msg.message.id);
return Ok(None);
}
match &msg.message.message {
Prevote(prevote) => {
telemetry!(CONSENSUS_INFO; "afg.received_prevote";
"voter" => ?format!("{}", msg.message.id),
"target_number" => ?prevote.target_number,
"target_hash" => ?prevote.target_hash,
);
},
Precommit(precommit) => {
telemetry!(CONSENSUS_INFO; "afg.received_precommit";
"voter" => ?format!("{}", msg.message.id),
"target_number" => ?precommit.target_number,
"target_hash" => ?precommit.target_hash,
);
},
};
Ok(Some(msg.message))
}
_ => {
debug!(target: "afg", "Skipping unknown message type");
return Ok(None);
}
}
})
.filter_map(|x| x)
.map_err(|()| Error::Network(format!("Failed to receive message on unbounded stream")))
}
pub(crate) struct OutgoingMessages<Block: BlockT, N: Network<Block>> {
round: u64,
set_id: u64,
locals: Option<(Arc<ed25519::Pair>, AuthorityId)>,
sender: mpsc::UnboundedSender<SignedMessage<Block>>,
network: N,
}
impl<Block: BlockT, N: Network<Block>> Sink for OutgoingMessages<Block, N>
{
type SinkItem = Message<Block>;
type SinkError = Error;
fn start_send(&mut self, msg: Message<Block>) -> StartSend<Message<Block>, Error> {
// when locals exist, sign messages on import
if let Some((ref pair, ref local_id)) = self.locals {
let encoded = localized_payload(self.round, self.set_id, &msg);
let signature = pair.sign(&encoded[..]);
let target_hash = msg.target().0.clone();
let signed = SignedMessage::<Block> {
message: msg,
signature,
id: local_id.clone(),
};
let message = GossipMessage::VoteOrPrecommit(VoteOrPrecommitMessage::<Block> {
message: signed.clone(),
round: self.round,
set_id: self.set_id,
});
// announce our block hash to peers and propagate the
// message.
self.network.announce(self.round, self.set_id, target_hash);
self.network.send_message(self.round, self.set_id, message.encode(), false);
// forward the message to the inner sender.
let _ = self.sender.unbounded_send(signed);
}
Ok(AsyncSink::Ready)
}
fn poll_complete(&mut self) -> Poll<(), Error> { Ok(Async::Ready(())) }
fn close(&mut self) -> Poll<(), Error> {
// ignore errors since we allow this inner sender to be closed already.
self.sender.close().or_else(|_| Ok(Async::Ready(())))
}
}
impl<Block: BlockT, N: Network<Block>> Drop for OutgoingMessages<Block, N> {
fn drop(&mut self) {
self.network.drop_round_messages(self.round, self.set_id);
}
}
/// A sink for outgoing messages. This signs the messages with the key,
/// if we are an authority. A stream for the signed messages is also returned.
///
/// A future can push unsigned messages into the sink. They will be automatically
/// broadcast to the network. The returned stream should be combined with other input.
pub(crate) fn outgoing_messages<Block: BlockT, N: Network<Block>>(
round: u64,
set_id: u64,
local_key: Option<Arc<ed25519::Pair>>,
voters: Arc<VoterSet<AuthorityId>>,
network: N,
) -> (
impl Stream<Item=SignedMessage<Block>,Error=Error>,
OutgoingMessages<Block, N>,
) {
let locals = local_key.and_then(|pair| {
let public = pair.public();
let id = AuthorityId(public.0);
if voters.contains_key(&id) {
Some((pair, id))
} else {
None
}
});
let (tx, rx) = mpsc::unbounded();
let outgoing = OutgoingMessages::<Block, N> {
round,
set_id,
network,
locals,
sender: tx,
};
let rx = rx.map_err(move |()| Error::Network(
format!("Failed to receive on unbounded receiver for round {}", round)
));
(rx, outgoing)
}
fn check_compact_commit<Block: BlockT>(
msg: CompactCommit<Block>,
voters: &VoterSet<AuthorityId>,
) -> Option<CompactCommit<Block>> {
if msg.precommits.len() != msg.auth_data.len() || msg.precommits.is_empty() {
debug!(target: "afg", "Skipping malformed compact commit");
return None;
}
// check signatures on all contained precommits.
for (_, ref id) in &msg.auth_data {
if !voters.contains_key(id) {
debug!(target: "afg", "Skipping commit containing unknown voter {}", id);
return None;
}
}
Some(msg)
}
/// A stream for incoming commit messages. This checks all the signatures on the
/// messages.
pub(crate) fn checked_commit_stream<Block: BlockT, S>(
inner: S,
voters: Arc<VoterSet<AuthorityId>>,
)
-> impl Stream<Item=(u64, CompactCommit<Block>),Error=Error> where
S: Stream<Item=Vec<u8>,Error=()>
{
inner
.filter_map(|raw| {
// this could be optimized by decoding piecewise.
let decoded = GossipMessage::<Block>::decode(&mut &raw[..]);
if decoded.is_none() {
trace!(target: "afg", "Skipping malformed commit message {:?}", raw);
}
decoded
})
.filter_map(move |msg| {
match msg {
GossipMessage::Commit(msg) => {
let round = msg.round;
let precommits_signed_by: Vec<String> =
msg.message.auth_data.iter().map(move |(_, a)| {
format!("{}", a)
}).collect();
telemetry!(CONSENSUS_INFO; "afg.received_commit";
"contains_precommits_signed_by" => ?precommits_signed_by,
"target_number" => ?msg.message.target_number,
"target_hash" => ?msg.message.target_hash,
);
check_compact_commit::<Block>(msg.message, &*voters).map(move |c| (round, c))
},
_ => {
debug!(target: "afg", "Skipping unknown message type");
return None;
}
}
})
.map_err(|()| Error::Network(format!("Failed to receive message on unbounded stream")))
}
/// An output sink for commit messages.
pub(crate) struct CommitsOut<Block: BlockT, N: Network<Block>> {
network: N,
set_id: u64,
_marker: ::std::marker::PhantomData<Block>,
is_voter: bool,
}
impl<Block: BlockT, N: Network<Block>> CommitsOut<Block, N> {
/// Create a new commit output stream.
pub(crate) fn new(network: N, set_id: u64, is_voter: bool) -> Self {
CommitsOut {
network,
set_id,
is_voter,
_marker: Default::default(),
}
}
}
impl<Block: BlockT, N: Network<Block>> Sink for CommitsOut<Block, N> {
type SinkItem = (u64, Commit<Block>);
type SinkError = Error;
fn start_send(&mut self, input: (u64, Commit<Block>)) -> StartSend<Self::SinkItem, Error> {
if !self.is_voter {
return Ok(AsyncSink::Ready);
}
let (round, commit) = input;
telemetry!(CONSENSUS_INFO; "afg.commit_issued";
"target_number" => ?commit.target_number, "target_hash" => ?commit.target_hash,
);
let (precommits, auth_data) = commit.precommits.into_iter()
.map(|signed| (signed.precommit, (signed.signature, signed.id)))
.unzip();
let compact_commit = CompactCommit::<Block> {
target_hash: commit.target_hash,
target_number: commit.target_number,
precommits,
auth_data
};
let message = GossipMessage::Commit(FullCommitMessage::<Block> {
round: round,
set_id: self.set_id,
message: compact_commit,
});
self.network.send_commit(round, self.set_id, Encode::encode(&message), false);
Ok(AsyncSink::Ready)
}
fn close(&mut self) -> Poll<(), Error> { Ok(Async::Ready(())) }
fn poll_complete(&mut self) -> Poll<(), Error> { Ok(Async::Ready(())) }
}
impl<Block: BlockT, N: Network<Block>> Drop for CommitsOut<Block, N> {
fn drop(&mut self) {
self.network.drop_set_messages(self.set_id);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,561 @@
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Communication streams for the polite-grandpa networking protocol.
//!
//! GRANDPA nodes communicate over a gossip network, where messages are not sent to
//! peers until they have reached a given round.
//!
//! Rather than expressing protocol rules,
//! polite-grandpa just carries a notion of impoliteness. Nodes which pass some arbitrary
//! threshold of impoliteness are removed. Messages are either costly, or beneficial.
//!
//! For instance, it is _impolite_ to send the same message more than once.
//! In the future, there will be a fallback for allowing sending the same message
//! under certain conditions that are used to un-stick the protocol.
use std::sync::Arc;
use grandpa::VoterSet;
use grandpa::Message::{Prevote, Precommit};
use futures::prelude::*;
use futures::sync::{oneshot, mpsc};
use log::{debug, trace};
use parity_codec::{Encode, Decode};
use substrate_primitives::{ed25519, Pair};
use substrate_telemetry::{telemetry, CONSENSUS_DEBUG, CONSENSUS_INFO};
use runtime_primitives::ConsensusEngineId;
use runtime_primitives::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, NumberFor};
use network::{consensus_gossip as network_gossip, Service as NetworkService,};
use network_gossip::ConsensusMessage;
use crate::{Error, Message, SignedMessage, Commit, CompactCommit};
use gossip::{
GossipMessage, FullCommitMessage, VoteOrPrecommitMessage, GossipValidator
};
use substrate_primitives::ed25519::{Public as AuthorityId, Signature as AuthoritySignature};
pub mod gossip;
/// The consensus engine ID of GRANDPA.
pub const GRANDPA_ENGINE_ID: ConsensusEngineId = [b'a', b'f', b'g', b'1'];
/// A handle to the network. This is generally implemented by providing some
/// handle to a gossip service or similar.
///
/// Intended to be a lightweight handle such as an `Arc`.
pub trait Network<Block: BlockT>: Clone {
/// A stream of input messages for a topic.
type In: Stream<Item=network_gossip::TopicNotification,Error=()>;
/// Get a stream of messages for a specific gossip topic.
fn messages_for(&self, topic: Block::Hash) -> Self::In;
/// Register a gossip validator.
fn register_validator(&self, validator: Arc<dyn network_gossip::Validator<Block>>);
/// Gossip a message out to all connected peers.
///
/// Force causes it to be sent to all peers, even if they've seen it already.
/// Only should be used in case of consensus stall.
fn gossip_message(&self, topic: Block::Hash, data: Vec<u8>, force: bool);
/// Send a message to a bunch of specific peers, even if they've seen it already.
fn send_message(&self, who: Vec<network::PeerId>, data: Vec<u8>);
/// Inform peers that a block with given hash should be downloaded.
fn announce(&self, block: Block::Hash);
}
/// Create a unique topic for a round and set-id combo.
pub(crate) fn round_topic<B: BlockT>(round: u64, set_id: u64) -> B::Hash {
<<B::Header as HeaderT>::Hashing as HashT>::hash(format!("{}-{}", set_id, round).as_bytes())
}
/// Create a unique topic for global messages on a set ID.
pub(crate) fn global_topic<B: BlockT>(set_id: u64) -> B::Hash {
<<B::Header as HeaderT>::Hashing as HashT>::hash(format!("{}-GLOBAL", set_id).as_bytes())
}
impl<B, S> Network<B> for Arc<NetworkService<B, S>> where
B: BlockT,
S: network::specialization::NetworkSpecialization<B>,
{
type In = NetworkStream;
fn messages_for(&self, topic: B::Hash) -> Self::In {
let (tx, rx) = oneshot::channel();
self.with_gossip(move |gossip, _| {
let inner_rx = gossip.messages_for(GRANDPA_ENGINE_ID, topic);
let _ = tx.send(inner_rx);
});
NetworkStream { outer: rx, inner: None }
}
fn register_validator(&self, validator: Arc<dyn network_gossip::Validator<B>>) {
self.with_gossip(
move |gossip, context| gossip.register_validator(context, GRANDPA_ENGINE_ID, validator)
)
}
fn gossip_message(&self, topic: B::Hash, data: Vec<u8>, force: bool) {
let msg = ConsensusMessage {
engine_id: GRANDPA_ENGINE_ID,
data,
};
self.with_gossip(
move |gossip, ctx| gossip.multicast(ctx, topic, msg, force)
)
}
fn send_message(&self, who: Vec<network::PeerId>, data: Vec<u8>) {
let msg = ConsensusMessage {
engine_id: GRANDPA_ENGINE_ID,
data,
};
self.with_gossip(move |gossip, ctx| for who in &who {
gossip.send_message(ctx, who, msg.clone())
})
}
fn announce(&self, block: B::Hash) {
self.announce_block(block)
}
}
/// A stream used by NetworkBridge in its implementation of Network.
pub struct NetworkStream {
inner: Option<mpsc::UnboundedReceiver<network_gossip::TopicNotification>>,
outer: oneshot::Receiver<mpsc::UnboundedReceiver<network_gossip::TopicNotification>>
}
impl Stream for NetworkStream {
type Item = network_gossip::TopicNotification;
type Error = ();
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(ref mut inner) = self.inner {
return inner.poll();
}
match self.outer.poll() {
Ok(futures::Async::Ready(mut inner)) => {
let poll_result = inner.poll();
self.inner = Some(inner);
poll_result
},
Ok(futures::Async::NotReady) => Ok(futures::Async::NotReady),
Err(_) => Err(())
}
}
}
/// Bridge between the underlying network service, gossiping consensus messages and Grandpa
pub(crate) struct NetworkBridge<B: BlockT, N: Network<B>> {
service: N,
validator: Arc<GossipValidator<B>>,
}
impl<B: BlockT, N: Network<B>> NetworkBridge<B, N> {
/// Create a new NetworkBridge to the given NetworkService
pub(crate) fn new(service: N, config: crate::Config) -> Self {
let validator = Arc::new(GossipValidator::new(config));
service.register_validator(validator.clone());
NetworkBridge { service, validator: validator }
}
/// Get the round messages for a round in a given set ID. These are signature-checked.
pub(crate) fn round_communication(
&self,
round: Round,
set_id: SetId,
voters: Arc<VoterSet<AuthorityId>>,
local_key: Option<Arc<ed25519::Pair>>,
has_voted: HasVoted,
) -> (
impl Stream<Item=SignedMessage<B>,Error=Error>,
impl Sink<SinkItem=Message<B>,SinkError=Error>,
) {
self.validator.note_round(round, set_id, &self.service);
let locals = local_key.and_then(|pair| {
let public = pair.public();
let id = AuthorityId(public.0);
if voters.contains_key(&id) {
Some((pair, id))
} else {
None
}
});
let topic = round_topic::<B>(round.0, set_id.0);
let incoming = self.service.messages_for(topic)
.filter_map(|notification| {
let decoded = GossipMessage::<B>::decode(&mut &notification.message[..]);
if decoded.is_none() {
debug!(target: "afg", "Skipping malformed message {:?}", notification);
}
decoded
})
.and_then(move |msg| {
match msg {
GossipMessage::VoteOrPrecommit(msg) => {
// check signature.
if !voters.contains_key(&msg.message.id) {
debug!(target: "afg", "Skipping message from unknown voter {}", msg.message.id);
return Ok(None);
}
match &msg.message.message {
Prevote(prevote) => {
telemetry!(CONSENSUS_INFO; "afg.received_prevote";
"voter" => ?format!("{}", msg.message.id),
"target_number" => ?prevote.target_number,
"target_hash" => ?prevote.target_hash,
);
},
Precommit(precommit) => {
telemetry!(CONSENSUS_INFO; "afg.received_precommit";
"voter" => ?format!("{}", msg.message.id),
"target_number" => ?precommit.target_number,
"target_hash" => ?precommit.target_hash,
);
},
};
Ok(Some(msg.message))
}
_ => {
debug!(target: "afg", "Skipping unknown message type");
return Ok(None);
}
}
})
.filter_map(|x| x)
.map_err(|()| Error::Network(format!("Failed to receive message on unbounded stream")));
let (tx, out_rx) = mpsc::unbounded();
let outgoing = OutgoingMessages::<B, N> {
round: round.0,
set_id: set_id.0,
network: self.service.clone(),
locals,
sender: tx,
has_voted,
};
let out_rx = out_rx.map_err(move |()| Error::Network(
format!("Failed to receive on unbounded receiver for round {}", round.0)
));
let incoming = incoming.select(out_rx);
(incoming, outgoing)
}
/// Set up the global communication streams.
pub(crate) fn global_communication(&self,
set_id: SetId,
voters: Arc<VoterSet<AuthorityId>>,
is_voter: bool
) -> (
impl Stream<Item = (u64, CompactCommit<B>), Error = Error>,
impl Sink<SinkItem = (u64, Commit<B>), SinkError = Error>,
) {
self.validator.note_set(set_id, &self.service);
let topic = global_topic::<B>(set_id.0);
let incoming = self.service.messages_for(topic)
.filter_map(|notification| {
// this could be optimized by decoding piecewise.
let decoded = GossipMessage::<B>::decode(&mut &notification.message[..]);
if decoded.is_none() {
trace!(target: "afg", "Skipping malformed commit message {:?}", notification);
}
decoded
})
.filter_map(move |msg| {
match msg {
GossipMessage::Commit(msg) => {
let round = msg.round;
let precommits_signed_by: Vec<String> =
msg.message.auth_data.iter().map(move |(_, a)| {
format!("{}", a)
}).collect();
telemetry!(CONSENSUS_INFO; "afg.received_commit";
"contains_precommits_signed_by" => ?precommits_signed_by,
"target_number" => ?msg.message.target_number,
"target_hash" => ?msg.message.target_hash,
);
check_compact_commit::<B>(msg.message, &*voters).map(move |c| (round.0, c))
},
_ => {
debug!(target: "afg", "Skipping unknown message type");
return None;
}
}
})
.map_err(|()| Error::Network(format!("Failed to receive message on unbounded stream")));
let outgoing = CommitsOut::<B, N>::new(
self.service.clone(),
set_id.0,
is_voter,
);
(incoming, outgoing)
}
pub(crate) fn note_commit_finalized(&self, number: NumberFor<B>) {
self.validator.note_commit_finalized(number, &self.service);
}
}
impl<B: BlockT, N: Network<B>> Clone for NetworkBridge<B, N> {
fn clone(&self) -> Self {
NetworkBridge {
service: self.service.clone(),
validator: Arc::clone(&self.validator),
}
}
}
fn localized_payload<E: Encode>(round: u64, set_id: u64, message: &E) -> Vec<u8> {
(message, round, set_id).encode()
}
/// Type-safe wrapper around u64 when indicating that it's a round number.
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Encode, Decode)]
pub struct Round(pub u64);
/// Type-safe wrapper around u64 when indicating that it's a set ID.
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Encode, Decode)]
pub struct SetId(pub u64);
// check a message.
pub(crate) fn check_message_sig<Block: BlockT>(
message: &Message<Block>,
id: &AuthorityId,
signature: &AuthoritySignature,
round: u64,
set_id: u64,
) -> Result<(), ()> {
let as_public = AuthorityId::from_raw(id.0);
let encoded_raw = localized_payload(round, set_id, message);
if ed25519::Pair::verify(signature, &encoded_raw, as_public) {
Ok(())
} else {
debug!(target: "afg", "Bad signature on message from {:?}", id);
Err(())
}
}
/// Whether we've voted already during a prior run of the program.
#[derive(Decode, Encode)]
pub(crate) enum HasVoted {
/// Has not voted already in this round.
#[codec(index = "0")]
No,
/// Has cast a proposal.
#[codec(index = "1")]
Proposed,
/// Has cast a prevote.
#[codec(index = "2")]
Prevoted,
/// Has cast a precommit (implies prevote.)
#[codec(index = "3")]
Precommitted,
}
impl HasVoted {
#[allow(unused)]
fn can_propose(&self) -> bool {
match *self {
HasVoted::No => true,
HasVoted::Proposed | HasVoted::Prevoted | HasVoted::Precommitted => false,
}
}
fn can_prevote(&self) -> bool {
match *self {
HasVoted::No | HasVoted::Proposed => true,
HasVoted::Prevoted | HasVoted::Precommitted => false,
}
}
fn can_precommit(&self) -> bool {
match *self {
HasVoted::No | HasVoted::Proposed | HasVoted::Prevoted => true,
HasVoted::Precommitted => false,
}
}
}
/// A sink for outgoing messages to the network.
struct OutgoingMessages<Block: BlockT, N: Network<Block>> {
round: u64,
set_id: u64,
locals: Option<(Arc<ed25519::Pair>, AuthorityId)>,
sender: mpsc::UnboundedSender<SignedMessage<Block>>,
network: N,
has_voted: HasVoted,
}
impl<Block: BlockT, N: Network<Block>> Sink for OutgoingMessages<Block, N>
{
type SinkItem = Message<Block>;
type SinkError = Error;
fn start_send(&mut self, msg: Message<Block>) -> StartSend<Message<Block>, Error> {
// only sign if we haven't voted in this round already.
let should_sign = match msg {
grandpa::Message::Prevote(_) => self.has_voted.can_prevote(),
grandpa::Message::Precommit(_) => self.has_voted.can_precommit(),
};
// when locals exist, sign messages on import
if let (true, &Some((ref pair, ref local_id))) = (should_sign, &self.locals) {
let encoded = localized_payload(self.round, self.set_id, &msg);
let signature = pair.sign(&encoded[..]);
let target_hash = msg.target().0.clone();
let signed = SignedMessage::<Block> {
message: msg,
signature,
id: local_id.clone(),
};
let message = GossipMessage::VoteOrPrecommit(VoteOrPrecommitMessage::<Block> {
message: signed.clone(),
round: Round(self.round),
set_id: SetId(self.set_id),
});
debug!(
target: "afg",
"Announcing block {} to peers which we voted on in round {} in set {}",
target_hash,
self.round,
self.set_id,
);
telemetry!(
CONSENSUS_DEBUG; "afg.announcing_blocks_to_voted_peers";
"block" => ?target_hash, "round" => ?self.round, "set_id" => ?self.set_id,
);
// announce our block hash to peers and propagate the
// message.
self.network.announce(target_hash);
let topic = round_topic::<Block>(self.round, self.set_id);
self.network.gossip_message(topic, message.encode(), false);
// forward the message to the inner sender.
let _ = self.sender.unbounded_send(signed);
}
Ok(AsyncSink::Ready)
}
fn poll_complete(&mut self) -> Poll<(), Error> { Ok(Async::Ready(())) }
fn close(&mut self) -> Poll<(), Error> {
// ignore errors since we allow this inner sender to be closed already.
self.sender.close().or_else(|_| Ok(Async::Ready(())))
}
}
fn check_compact_commit<Block: BlockT>(
msg: CompactCommit<Block>,
voters: &VoterSet<AuthorityId>,
) -> Option<CompactCommit<Block>> {
if msg.precommits.len() != msg.auth_data.len() || msg.precommits.is_empty() {
debug!(target: "afg", "Skipping malformed compact commit");
return None;
}
// check signatures on all contained precommits.
for (_, ref id) in &msg.auth_data {
if !voters.contains_key(id) {
debug!(target: "afg", "Skipping commit containing unknown voter {}", id);
return None;
}
}
Some(msg)
}
/// An output sink for commit messages.
struct CommitsOut<Block: BlockT, N: Network<Block>> {
network: N,
set_id: SetId,
is_voter: bool,
_marker: ::std::marker::PhantomData<Block>,
}
impl<Block: BlockT, N: Network<Block>> CommitsOut<Block, N> {
/// Create a new commit output stream.
pub(crate) fn new(network: N, set_id: u64, is_voter: bool) -> Self {
CommitsOut {
network,
set_id: SetId(set_id),
is_voter,
_marker: Default::default(),
}
}
}
impl<Block: BlockT, N: Network<Block>> Sink for CommitsOut<Block, N> {
type SinkItem = (u64, Commit<Block>);
type SinkError = Error;
fn start_send(&mut self, input: (u64, Commit<Block>)) -> StartSend<Self::SinkItem, Error> {
if !self.is_voter {
return Ok(AsyncSink::Ready);
}
let (round, commit) = input;
let round = Round(round);
telemetry!(CONSENSUS_INFO; "afg.commit_issued";
"target_number" => ?commit.target_number, "target_hash" => ?commit.target_hash,
);
let (precommits, auth_data) = commit.precommits.into_iter()
.map(|signed| (signed.precommit, (signed.signature, signed.id)))
.unzip();
let compact_commit = CompactCommit::<Block> {
target_hash: commit.target_hash,
target_number: commit.target_number,
precommits,
auth_data
};
let message = GossipMessage::Commit(FullCommitMessage::<Block> {
round: round,
set_id: self.set_id,
message: compact_commit,
});
let topic = global_topic::<Block>(self.set_id.0);
self.network.gossip_message(topic, message.encode(), false);
Ok(AsyncSink::Ready)
}
fn close(&mut self) -> Poll<(), Error> { Ok(Async::Ready(())) }
fn poll_complete(&mut self) -> Poll<(), Error> { Ok(Async::Ready(())) }
}
@@ -82,7 +82,7 @@ pub(crate) struct Environment<B, E, Block: BlockT, N: Network<Block>, RA> {
pub(crate) config: Config,
pub(crate) authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
pub(crate) consensus_changes: SharedConsensusChanges<Block::Hash, NumberFor<Block>>,
pub(crate) network: N,
pub(crate) network: crate::communication::NetworkBridge<Block, N>,
pub(crate) set_id: u64,
pub(crate) last_completed: LastCompletedRound<Block::Hash, NumberFor<Block>>,
}
@@ -231,32 +231,24 @@ impl<B, E, Block: BlockT<Hash=H256>, N, RA> voter::Environment<Block::Hash, Numb
let prevote_timer = Delay::new(now + self.config.gossip_duration * 2);
let precommit_timer = Delay::new(now + self.config.gossip_duration * 4);
let incoming = crate::communication::checked_message_stream::<Block, _>(
self.network.messages_for(round, self.set_id),
self.voters.clone(),
);
let local_key = self.config.local_key.as_ref()
.filter(|pair| self.voters.contains_key(&pair.public().into()));
let (out_rx, outgoing) = crate::communication::outgoing_messages::<Block, _>(
round,
self.set_id,
local_key.cloned(),
let (incoming, outgoing) = self.network.round_communication(
crate::communication::Round(round),
crate::communication::SetId(self.set_id),
self.voters.clone(),
self.network.clone(),
local_key.cloned(),
crate::communication::HasVoted::No,
);
// schedule incoming messages from the network to be held until
// corresponding blocks are imported.
let incoming = UntilVoteTargetImported::new(
let incoming = Box::new(UntilVoteTargetImported::new(
self.inner.import_notification_stream(),
self.inner.clone(),
incoming,
);
// join incoming network messages with locally originating ones.
let incoming = Box::new(out_rx.select(incoming).map_err(Into::into));
).map_err(Into::into));
// schedule network message cleanup when sink drops.
let outgoing = Box::new(outgoing.sink_map_err(Into::into));
@@ -305,7 +297,7 @@ impl<B, E, Block: BlockT<Hash=H256>, N, RA> voter::Environment<Block::Hash, Numb
return Ok(());
}
finalize_block(
let res = finalize_block(
&*self.inner,
&self.authority_set,
&self.consensus_changes,
@@ -313,7 +305,13 @@ impl<B, E, Block: BlockT<Hash=H256>, N, RA> voter::Environment<Block::Hash, Numb
hash,
number,
(round, commit).into(),
)
);
if let Ok(_) = res {
self.network.note_commit_finalized(number);
}
res
}
fn round_commit_timer(&self) -> Self::Timer {
+22 -392
View File
@@ -53,32 +53,28 @@
//! included in the newly-finalized chain.
use futures::prelude::*;
use log::{debug, info, warn, trace};
use futures::sync::{self, mpsc, oneshot};
use log::{debug, info, warn};
use futures::sync::mpsc;
use client::{
BlockchainEvents, CallExecutor, Client, backend::Backend,
error::Error as ClientError,
};
use client::blockchain::HeaderBackend;
use parity_codec::{Encode, Decode};
use parity_codec::Encode;
use runtime_primitives::traits::{
NumberFor, Block as BlockT, Header as HeaderT, DigestFor, ProvideRuntimeApi, Hash as HashT,
DigestItemFor, DigestItem,
NumberFor, Block as BlockT, DigestFor, ProvideRuntimeApi, DigestItemFor, DigestItem,
};
use fg_primitives::GrandpaApi;
use inherents::InherentDataProviders;
use runtime_primitives::generic::BlockId;
use substrate_primitives::{ed25519, H256, Blake2Hasher, Pair};
use substrate_telemetry::{telemetry, CONSENSUS_TRACE, CONSENSUS_DEBUG, CONSENSUS_WARN, CONSENSUS_INFO};
use substrate_primitives::{ed25519, H256, Pair, Blake2Hasher};
use substrate_telemetry::{telemetry, CONSENSUS_INFO, CONSENSUS_DEBUG, CONSENSUS_WARN};
use srml_finality_tracker;
use grandpa::Error as GrandpaError;
use grandpa::{voter, round::State as RoundState, BlockNumberOps, VoterSet};
use network::Service as NetworkService;
use network::consensus_gossip as network_gossip;
use std::fmt;
use std::sync::Arc;
use std::time::Duration;
@@ -99,21 +95,20 @@ mod until_imported;
mod service_integration;
#[cfg(feature="service-integration")]
pub use service_integration::{LinkHalfForService, BlockImportForService};
pub use communication::Network;
pub use finality_proof::{prove_finality, check_finality_proof};
use aux_schema::{PersistentData, VoterSetState};
use environment::Environment;
pub use finality_proof::{prove_finality, check_finality_proof};
use import::GrandpaBlockImport;
use until_imported::UntilCommitBlocksImported;
use communication::NetworkBridge;
use ed25519::{Public as AuthorityId, Signature as AuthoritySignature};
#[cfg(test)]
mod tests;
const GRANDPA_ENGINE_ID: runtime_primitives::ConsensusEngineId = [b'a', b'f', b'g', b'1'];
const MESSAGE_ROUND_TOLERANCE: u64 = 2;
/// A GRANDPA message for a substrate chain.
pub type Message<Block> = grandpa::Message<<Block as BlockT>::Hash, NumberFor<Block>>;
/// A signed message.
@@ -124,24 +119,6 @@ pub type SignedMessage<Block> = grandpa::SignedMessage<
AuthorityId,
>;
/// Grandpa gossip message type.
/// This is the root type that gets encoded and sent on the network.
#[derive(Debug, Encode, Decode)]
pub enum GossipMessage<Block: BlockT> {
/// Grandpa message with round and set info.
VoteOrPrecommit(VoteOrPrecommitMessage<Block>),
/// Grandpa commit message with round and set info.
Commit(FullCommitMessage<Block>),
}
/// Network level message with topic information.
#[derive(Debug, Encode, Decode)]
pub struct VoteOrPrecommitMessage<Block: BlockT> {
pub round: u64,
pub set_id: u64,
pub message: SignedMessage<Block>,
}
/// A prevote message for this chain's block type.
pub type Prevote<Block> = grandpa::Prevote<<Block as BlockT>::Hash, NumberFor<Block>>;
/// A precommit message for this chain's block type.
@@ -161,14 +138,6 @@ pub type CompactCommit<Block> = grandpa::CompactCommit<
AuthorityId
>;
/// Network level commit message with topic information.
#[derive(Debug, Encode, Decode)]
pub struct FullCommitMessage<Block: BlockT> {
pub round: u64,
pub set_id: u64,
pub message: CompactCommit<Block>,
}
/// Configuration for the GRANDPA service.
#[derive(Clone)]
pub struct Config {
@@ -219,337 +188,6 @@ impl From<ClientError> for Error {
}
}
/// A stream used by NetworkBridge in its implementation of Network.
pub struct NetworkStream {
inner: Option<mpsc::UnboundedReceiver<Vec<u8>>>,
outer: oneshot::Receiver<mpsc::UnboundedReceiver<Vec<u8>>>
}
impl Stream for NetworkStream {
type Item = Vec<u8>;
type Error = ();
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(ref mut inner) = self.inner {
return inner.poll();
}
match self.outer.poll() {
Ok(futures::Async::Ready(mut inner)) => {
let poll_result = inner.poll();
self.inner = Some(inner);
poll_result
},
Ok(futures::Async::NotReady) => Ok(futures::Async::NotReady),
Err(_) => Err(())
}
}
}
struct TopicTracker {
min_live_round: u64,
max_round: u64,
set_id: u64,
}
impl TopicTracker {
fn is_expired(&self, round: u64, set_id: u64) -> bool {
if set_id < self.set_id {
trace!(target: "afg", "Expired: Message with expired set_id {} (ours {})", set_id, self.set_id);
telemetry!(CONSENSUS_TRACE; "afg.expired_set_id";
"set_id" => ?set_id, "ours" => ?self.set_id
);
return true;
} else if set_id == self.set_id + 1 {
// allow a few first rounds of future set.
if round > MESSAGE_ROUND_TOLERANCE {
trace!(target: "afg", "Expired: Message too far in the future set, round {} (ours set_id {})", round, self.set_id);
telemetry!(CONSENSUS_TRACE; "afg.expired_msg_too_far_in_future_set";
"round" => ?round, "ours" => ?self.set_id
);
return true;
}
} else if set_id == self.set_id {
if round < self.min_live_round.saturating_sub(MESSAGE_ROUND_TOLERANCE) {
trace!(target: "afg", "Expired: Message round is out of bounds {} (ours {}-{})", round, self.min_live_round, self.max_round);
telemetry!(CONSENSUS_TRACE; "afg.msg_round_oob";
"round" => ?round, "our_min_live_round" => ?self.min_live_round, "our_max_round" => ?self.max_round
);
return true;
}
} else {
trace!(target: "afg", "Expired: Message in invalid future set {} (ours {})", set_id, self.set_id);
telemetry!(CONSENSUS_TRACE; "afg.expired_msg_in_invalid_future_set";
"set_id" => ?set_id, "ours" => ?self.set_id
);
return true;
}
false
}
}
struct GossipValidator<Block: BlockT> {
rounds: parking_lot::RwLock<TopicTracker>,
_marker: ::std::marker::PhantomData<Block>,
}
impl<Block: BlockT> GossipValidator<Block> {
fn new() -> GossipValidator<Block> {
GossipValidator {
rounds: parking_lot::RwLock::new(TopicTracker {
min_live_round: 0,
max_round: 0,
set_id: 0,
}),
_marker: Default::default(),
}
}
fn note_round(&self, round: u64, set_id: u64) {
let mut rounds = self.rounds.write();
if set_id > rounds.set_id {
rounds.set_id = set_id;
rounds.max_round = 0;
rounds.min_live_round = 0;
}
rounds.max_round = rounds.max_round.max(round);
}
fn note_set(&self, _set_id: u64) {
}
fn drop_round(&self, round: u64, set_id: u64) {
let mut rounds = self.rounds.write();
if set_id == rounds.set_id && round >= rounds.min_live_round {
rounds.min_live_round = round + 1;
}
}
fn drop_set(&self, _set_id: u64) {
}
fn is_expired(&self, round: u64, set_id: u64) -> bool {
self.rounds.read().is_expired(round, set_id)
}
fn validate_round_message(&self, full: VoteOrPrecommitMessage<Block>)
-> network_gossip::ValidationResult<Block::Hash>
{
if self.is_expired(full.round, full.set_id) {
return network_gossip::ValidationResult::Expired;
}
if let Err(()) = communication::check_message_sig::<Block>(
&full.message.message,
&full.message.id,
&full.message.signature,
full.round,
full.set_id
) {
debug!(target: "afg", "Bad message signature {}", full.message.id);
telemetry!(CONSENSUS_DEBUG; "afg.bad_msg_signature"; "signature" => ?full.message.id);
return network_gossip::ValidationResult::Invalid;
}
let topic = message_topic::<Block>(full.round, full.set_id);
network_gossip::ValidationResult::Valid(topic)
}
fn validate_commit_message(&self, full: FullCommitMessage<Block>)
-> network_gossip::ValidationResult<Block::Hash>
{
use grandpa::Message as GrandpaMessage;
if self.is_expired(full.round, full.set_id) {
return network_gossip::ValidationResult::Expired;
}
if full.message.precommits.len() != full.message.auth_data.len() || full.message.precommits.is_empty() {
debug!(target: "afg", "Malformed compact commit");
telemetry!(CONSENSUS_DEBUG; "afg.malformed_compact_commit";
"precommits_len" => ?full.message.precommits.len(),
"auth_data_len" => ?full.message.auth_data.len(),
"precommits_is_empty" => ?full.message.precommits.is_empty(),
);
return network_gossip::ValidationResult::Invalid;
}
// check signatures on all contained precommits.
for (precommit, &(ref sig, ref id)) in full.message.precommits.iter().zip(&full.message.auth_data) {
if let Err(()) = communication::check_message_sig::<Block>(
&GrandpaMessage::Precommit(precommit.clone()),
id,
sig,
full.round,
full.set_id,
) {
debug!(target: "afg", "Bad commit message signature {}", id);
telemetry!(CONSENSUS_DEBUG; "afg.bad_commit_msg_signature"; "id" => ?id);
return network_gossip::ValidationResult::Invalid;
}
}
let topic = commit_topic::<Block>(full.set_id);
let precommits_signed_by: Vec<String> = full.message.auth_data.iter().map(move |(_, a)| {
format!("{}", a)
}).collect();
telemetry!(CONSENSUS_INFO; "afg.received_commit_msg";
"contains_precommits_signed_by" => ?precommits_signed_by,
"round" => ?full.round,
"set_id" => ?full.set_id,
"topic" => ?topic,
"block_hash" => ?full.message,
);
network_gossip::ValidationResult::Valid(topic)
}
}
impl<Block: BlockT> network_gossip::Validator<Block::Hash> for GossipValidator<Block> {
fn validate(&self, mut data: &[u8]) -> network_gossip::ValidationResult<Block::Hash> {
match GossipMessage::<Block>::decode(&mut data) {
Some(GossipMessage::VoteOrPrecommit(message)) => self.validate_round_message(message),
Some(GossipMessage::Commit(message)) => self.validate_commit_message(message),
None => {
debug!(target: "afg", "Error decoding message");
telemetry!(CONSENSUS_DEBUG; "afg.err_decoding_msg"; "" => "");
network_gossip::ValidationResult::Invalid
}
}
}
fn message_expired<'a>(&'a self) -> Box<FnMut(Block::Hash, &[u8]) -> bool + 'a> {
let rounds = self.rounds.read();
Box::new(move |_topic, mut data| {
match GossipMessage::<Block>::decode(&mut data) {
None => true,
Some(GossipMessage::Commit(full)) => rounds.is_expired(full.round, full.set_id),
Some(GossipMessage::VoteOrPrecommit(full)) =>
rounds.is_expired(full.round, full.set_id),
}
})
}
}
/// A handle to the network. This is generally implemented by providing some
/// handle to a gossip service or similar.
///
/// Intended to be a lightweight handle such as an `Arc`.
pub trait Network<Block: BlockT>: Clone {
/// A stream of input messages for a topic.
type In: Stream<Item=Vec<u8>,Error=()>;
/// Get a stream of messages for a specific round. This stream should
/// never logically conclude.
fn messages_for(&self, round: u64, set_id: u64) -> Self::In;
/// Send a message at a specific round out.
fn send_message(&self, round: u64, set_id: u64, message: Vec<u8>, force: bool);
/// Clean up messages for a round.
fn drop_round_messages(&self, round: u64, set_id: u64);
/// Clean up messages for a given authority set id (e.g. commit messages).
fn drop_set_messages(&self, set_id: u64);
/// Get a stream of commit messages for a specific set-id. This stream
/// should never logically conclude.
fn commit_messages(&self, set_id: u64) -> Self::In;
/// Send message over the commit channel.
fn send_commit(&self, round: u64, set_id: u64, message: Vec<u8>, force: bool);
/// Inform peers that a block with given hash should be downloaded.
fn announce(&self, round: u64, set_id: u64, block: Block::Hash);
}
/// Bridge between NetworkService, gossiping consensus messages and Grandpa
pub struct NetworkBridge<B: BlockT, S: network::specialization::NetworkSpecialization<B>> {
service: Arc<NetworkService<B, S>>,
validator: Arc<GossipValidator<B>>,
}
impl<B: BlockT, S: network::specialization::NetworkSpecialization<B>> NetworkBridge<B, S> {
/// Create a new NetworkBridge to the given NetworkService
pub fn new(service: Arc<NetworkService<B, S>>) -> Self {
let validator = Arc::new(GossipValidator::new());
let v = validator.clone();
service.with_gossip(move |gossip, _| {
gossip.register_validator(GRANDPA_ENGINE_ID, v);
});
NetworkBridge { service, validator: validator }
}
}
impl<B: BlockT, S: network::specialization::NetworkSpecialization<B>,> Clone for NetworkBridge<B, S> {
fn clone(&self) -> Self {
NetworkBridge {
service: Arc::clone(&self.service),
validator: Arc::clone(&self.validator),
}
}
}
fn message_topic<B: BlockT>(round: u64, set_id: u64) -> B::Hash {
<<B::Header as HeaderT>::Hashing as HashT>::hash(format!("{}-{}", set_id, round).as_bytes())
}
fn commit_topic<B: BlockT>(set_id: u64) -> B::Hash {
<<B::Header as HeaderT>::Hashing as HashT>::hash(format!("{}-COMMITS", set_id).as_bytes())
}
impl<B: BlockT, S: network::specialization::NetworkSpecialization<B>,> Network<B> for NetworkBridge<B, S> {
type In = NetworkStream;
fn messages_for(&self, round: u64, set_id: u64) -> Self::In {
self.validator.note_round(round, set_id);
let (tx, rx) = sync::oneshot::channel();
self.service.with_gossip(move |gossip, _| {
let inner_rx = gossip.messages_for(GRANDPA_ENGINE_ID, message_topic::<B>(round, set_id));
let _ = tx.send(inner_rx);
});
NetworkStream { outer: rx, inner: None }
}
fn send_message(&self, round: u64, set_id: u64, message: Vec<u8>, force: bool) {
let topic = message_topic::<B>(round, set_id);
self.service.gossip_consensus_message(topic, GRANDPA_ENGINE_ID, message, force);
}
fn drop_round_messages(&self, round: u64, set_id: u64) {
self.validator.drop_round(round, set_id);
self.service.with_gossip(move |gossip, _| gossip.collect_garbage());
}
fn drop_set_messages(&self, set_id: u64) {
self.validator.drop_set(set_id);
self.service.with_gossip(move |gossip, _| gossip.collect_garbage());
}
fn commit_messages(&self, set_id: u64) -> Self::In {
self.validator.note_set(set_id);
let (tx, rx) = sync::oneshot::channel();
self.service.with_gossip(move |gossip, _| {
let inner_rx = gossip.messages_for(GRANDPA_ENGINE_ID, commit_topic::<B>(set_id));
let _ = tx.send(inner_rx);
});
NetworkStream { outer: rx, inner: None }
}
fn send_commit(&self, _round: u64, set_id: u64, message: Vec<u8>, force: bool) {
let topic = commit_topic::<B>(set_id);
self.service.gossip_consensus_message(topic, GRANDPA_ENGINE_ID, message, force);
}
fn announce(&self, round: u64, _set_id: u64, block: B::Hash) {
debug!(target: "afg", "Announcing block {} to peers which we voted on in round {}", block, round);
telemetry!(CONSENSUS_DEBUG; "afg.announcing_blocks_to_voted_peers";
"block" => ?block, "round" => ?round
);
self.service.announce_block(block)
}
}
/// Something which can determine if a block is known.
pub trait BlockStatus<Block: BlockT> {
/// Return `Ok(Some(number))` or `Ok(None)` depending on whether the block
@@ -698,11 +336,11 @@ pub fn block_import<B, E, Block: BlockT<Hash=H256>, RA, PRA>(
}
fn committer_communication<Block: BlockT<Hash=H256>, B, E, N, RA>(
local_key: Option<Arc<ed25519::Pair>>,
local_key: Option<&Arc<ed25519::Pair>>,
set_id: u64,
voters: &Arc<VoterSet<AuthorityId>>,
client: &Arc<Client<B, E, Block, RA>>,
network: &N,
network: &NetworkBridge<Block, N>,
) -> (
impl Stream<
Item = (u64, ::grandpa::CompactCommit<H256, NumberFor<Block>, AuthoritySignature, AuthorityId>),
@@ -720,10 +358,15 @@ fn committer_communication<Block: BlockT<Hash=H256>, B, E, N, RA>(
NumberFor<Block>: BlockNumberOps,
DigestItemFor<Block>: DigestItem<AuthorityId=AuthorityId>,
{
let is_voter = local_key
.map(|pair| voters.contains_key(&pair.public().into()))
.unwrap_or(false);
// verification stream
let commit_in = crate::communication::checked_commit_stream::<Block, _>(
network.commit_messages(set_id),
let (commit_in, commit_out) = network.global_communication(
communication::SetId(set_id),
voters.clone(),
is_voter,
);
// block commit messages until relevant blocks are imported.
@@ -733,16 +376,6 @@ fn committer_communication<Block: BlockT<Hash=H256>, B, E, N, RA>(
commit_in,
);
let is_voter = local_key
.map(|pair| voters.contains_key(&pair.public().into()))
.unwrap_or(false);
let commit_out = crate::communication::CommitsOut::<Block, _>::new(
network.clone(),
set_id,
is_voter,
);
let commit_in = commit_in.map_err(Into::into);
let commit_out = commit_out.sink_map_err(Into::into);
@@ -800,20 +433,18 @@ pub fn run_grandpa<B, E, Block: BlockT<Hash=H256>, N, RA>(
{
use futures::future::{self, Loop as FutureLoop};
let network = NetworkBridge::new(network, config.clone());
let LinkHalf {
client,
persistent_data,
voter_commands_rx,
} = link;
// we shadow network with the wrapping/rebroadcasting network to avoid
// accidental reuse.
let (broadcast_worker, network) = communication::rebroadcasting_network(network);
let PersistentData { authority_set, set_state, consensus_changes } = persistent_data;
register_finality_tracker_inherent_data_provider(client.clone(), &inherent_data_providers)?;
let voters = authority_set.current_authorities();
let initial_environment = Arc::new(Environment {
inner: client.clone(),
config: config.clone(),
@@ -846,7 +477,7 @@ pub fn run_grandpa<B, E, Block: BlockT<Hash=H256>, N, RA>(
);
let committer_data = committer_communication(
config.local_key.clone(),
config.local_key.as_ref(),
env.set_id,
&env.voters,
&client,
@@ -962,8 +593,7 @@ pub fn run_grandpa<B, E, Block: BlockT<Hash=H256>, N, RA>(
});
let voter_work = voter_work
.join(broadcast_worker)
.map(|((), ())| ())
.map(|_| ())
.map_err(|e| {
warn!("GRANDPA Voter failed: {:?}", e);
telemetry!(CONSENSUS_WARN; "afg.voter_failed"; "e" => ?e);
+37 -65
View File
@@ -20,6 +20,7 @@ use super::*;
use network::test::{Block, DummySpecialization, Hash, TestNetFactory, Peer, PeersClient};
use network::test::{PassThroughVerifier};
use network::config::{ProtocolConfig, Roles};
use network::consensus_gossip as network_gossip;
use parking_lot::Mutex;
use tokio::runtime::current_thread;
use keyring::AuthorityKeyring;
@@ -33,11 +34,12 @@ use consensus_common::{BlockOrigin, ForkChoiceStrategy, ImportedAux, ImportBlock
use consensus_common::import_queue::{SharedBlockImport, SharedJustificationImport};
use std::collections::{HashMap, HashSet};
use std::result;
use runtime_primitives::traits::{ApiRef, ProvideRuntimeApi};
use runtime_primitives::traits::{ApiRef, ProvideRuntimeApi, Header as HeaderT};
use runtime_primitives::generic::BlockId;
use substrate_primitives::{NativeOrEncoded, ExecutionContext};
use authorities::AuthoritySet;
use communication::GRANDPA_ENGINE_ID;
use consensus_changes::ConsensusChanges;
type PeerData =
@@ -137,102 +139,72 @@ impl TestNetFactory for GrandpaTestNet {
struct MessageRouting {
inner: Arc<Mutex<GrandpaTestNet>>,
peer_id: usize,
validator: Arc<GossipValidator<Block>>,
}
impl MessageRouting {
fn new(inner: Arc<Mutex<GrandpaTestNet>>, peer_id: usize,) -> Self {
let validator = Arc::new(GossipValidator::new());
let v = validator.clone();
{
let inner = inner.lock();
let peer = inner.peer(peer_id);
peer.with_gossip(move |gossip, _| {
gossip.register_validator(GRANDPA_ENGINE_ID, v);
});
}
MessageRouting {
inner,
peer_id,
validator,
}
}
fn drop_messages(&self, topic: Hash) {
let inner = self.inner.lock();
let peer = inner.peer(self.peer_id);
peer.consensus_gossip_collect_garbage_for_topic(topic);
}
}
fn make_topic(round: u64, set_id: u64) -> Hash {
message_topic::<Block>(round, set_id)
}
fn make_commit_topic(set_id: u64) -> Hash {
commit_topic::<Block>(set_id)
}
impl Network<Block> for MessageRouting {
type In = Box<Stream<Item=Vec<u8>,Error=()> + Send>;
type In = Box<Stream<Item=network_gossip::TopicNotification, Error=()> + Send>;
fn messages_for(&self, round: u64, set_id: u64) -> Self::In {
self.validator.note_round(round, set_id);
/// Get a stream of messages for a specific gossip topic.
fn messages_for(&self, topic: Hash) -> Self::In {
let inner = self.inner.lock();
let peer = inner.peer(self.peer_id);
let messages = peer.consensus_gossip_messages_for(
GRANDPA_ENGINE_ID,
make_topic(round, set_id),
topic,
);
let messages = messages.map_err(
move |_| panic!("Messages for round {} dropped too early", round)
move |_| panic!("Messages for topic {} dropped too early", topic)
);
Box::new(messages)
}
fn send_message(&self, round: u64, set_id: u64, message: Vec<u8>, force: bool) {
let inner = self.inner.lock();
inner.peer(self.peer_id)
.gossip_message(make_topic(round, set_id), GRANDPA_ENGINE_ID, message, force);
}
fn drop_round_messages(&self, round: u64, set_id: u64) {
self.validator.drop_round(round, set_id);
let topic = make_topic(round, set_id);
self.drop_messages(topic);
}
fn drop_set_messages(&self, set_id: u64) {
self.validator.drop_set(set_id);
let topic = make_commit_topic(set_id);
self.drop_messages(topic);
}
fn commit_messages(&self, set_id: u64) -> Self::In {
self.validator.note_set(set_id);
fn register_validator(&self, v: Arc<dyn network_gossip::Validator<Block>>) {
let inner = self.inner.lock();
let peer = inner.peer(self.peer_id);
let messages = peer.consensus_gossip_messages_for(
GRANDPA_ENGINE_ID,
make_commit_topic(set_id),
);
let messages = messages.map_err(
move |_| panic!("Commit messages for set {} dropped too early", set_id)
);
Box::new(messages)
peer.with_gossip(move |gossip, context| {
gossip.register_validator(context, GRANDPA_ENGINE_ID, v);
});
}
fn send_commit(&self, _round: u64, set_id: u64, message: Vec<u8>, force: bool) {
fn gossip_message(&self, topic: Hash, data: Vec<u8>, force: bool) {
let inner = self.inner.lock();
inner.peer(self.peer_id)
.gossip_message(make_commit_topic(set_id), GRANDPA_ENGINE_ID, message, force);
inner.peer(self.peer_id).gossip_message(
topic,
GRANDPA_ENGINE_ID,
data,
force,
);
}
fn announce(&self, _round: u64, _set_id: u64, _block: H256) {
fn send_message(&self, who: Vec<network::PeerId>, data: Vec<u8>) {
let inner = self.inner.lock();
let peer = inner.peer(self.peer_id);
peer.with_gossip(move |gossip, ctx| for who in &who {
gossip.send_message(
ctx,
who,
network_gossip::ConsensusMessage {
engine_id: GRANDPA_ENGINE_ID,
data: data.clone(),
}
)
})
}
fn announce(&self, _block: Hash) {
}
}