// Copyright 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 .
//! Gossip and politeness for polite-grandpa.
//!
//! This module implements the following message types:
//! #### Neighbor Packet
//!
//! The neighbor packet is sent to only our neighbors. It contains this information
//!
//! - Current Round
//! - Current voter set ID
//! - Last finalized hash from commit messages.
//!
//! If a peer is at a given voter set, it is impolite to send messages from
//! an earlier voter set. It is extremely impolite to send messages
//! from a future voter set. "future-set" messages can be dropped and ignored.
//!
//! If a peer is at round r, is impolite to send messages about r-2 or earlier and extremely
//! impolite to send messages about r+1 or later. "future-round" messages can
//! be dropped and ignored.
//!
//! It is impolite to send a neighbor packet which moves backwards in protocol state.
//!
//! This is beneficial if it conveys some progress in the protocol state of the peer.
//!
//! #### Prevote / Precommit
//!
//! These are votes within a round. Noting that we receive these messages
//! from our peers who are not necessarily voters, we have to account the benefit
//! based on what they might have seen.
//!
//! #### Propose
//!
//! This is a broadcast by a known voter of the last-round estimate.
//! #### Commit
//!
//! These are used to announce past agreement of finality.
//!
//! It is impolite to send commits which are earlier than the last commit
//! sent. It is especially impolite to send commits which are invalid, or from
//! a different Set ID than the receiving peer has indicated.
//!
//! Sending a commit is polite when it may finalize something that the receiving peer
//! was not aware of.
//!
//! ## Expiration
//!
//! We keep some amount of recent rounds' messages, but do not accept new ones from rounds
//! older than our current_round - 1.
//!
//! ## Message Validation
//!
//! We only send polite messages to peers,
use runtime_primitives::traits::{NumberFor, Block as BlockT, Zero};
use network::consensus_gossip::{self as network_gossip, MessageIntent, ValidatorContext};
use network::{config::Roles, PeerId};
use parity_codec::{Encode, Decode};
use substrate_telemetry::{telemetry, CONSENSUS_DEBUG};
use log::{trace, debug, warn};
use futures::prelude::*;
use futures::sync::mpsc;
use crate::{CompactCommit, SignedMessage};
use super::{cost, benefit, Round, SetId};
use std::collections::{HashMap, VecDeque};
use std::time::{Duration, Instant};
const REBROADCAST_AFTER: Duration = Duration::from_secs(60 * 5);
/// An outcome of examining a message.
#[derive(Debug, PartialEq, Clone, Copy)]
enum Consider {
/// Accept the message.
Accept,
/// Message is too early. Reject.
RejectPast,
/// Message is from the future. Reject.
RejectFuture,
}
/// A view of protocol state.
#[derive(Debug)]
struct View {
round: Round, // the current round we are at.
set_id: SetId, // the current voter set id.
last_commit: Option, // commit-finalized block height, if any.
}
impl Default for View {
fn default() -> Self {
View {
round: Round(0),
set_id: SetId(0),
last_commit: None,
}
}
}
impl View {
/// Update the set ID. implies a reset to round 0.
fn update_set(&mut self, set_id: SetId) {
if set_id != self.set_id {
self.set_id = set_id;
self.round = Round(0);
}
}
/// Consider a round and set ID combination under a current view.
fn consider_vote(&self, round: Round, set_id: SetId) -> Consider {
// only from current set
if set_id < self.set_id { return Consider::RejectPast }
if set_id > self.set_id { return Consider::RejectFuture }
// only r-1 ... r+1
if round.0 > self.round.0.saturating_add(1) { return Consider::RejectFuture }
if round.0 < self.round.0.saturating_sub(1) { return Consider::RejectPast }
Consider::Accept
}
/// Consider a set-id global message. Rounds are not taken into account, but are implicitly
/// because we gate on finalization of a further block than a previous commit.
fn consider_global(&self, set_id: SetId, number: N) -> Consider {
// only from current set
if set_id < self.set_id { return Consider::RejectPast }
if set_id > self.set_id { return Consider::RejectFuture }
// only commits which claim to prove a higher block number than
// the one we're aware of.
match self.last_commit {
None => Consider::Accept,
Some(ref num) => if num < &number {
Consider::Accept
} else {
Consider::RejectPast
}
}
}
}
const KEEP_RECENT_ROUNDS: usize = 3;
/// Tracks topics we keep messages for.
struct KeepTopics {
current_set: SetId,
rounds: VecDeque<(Round, SetId)>,
reverse_map: HashMap, SetId)>
}
impl KeepTopics {
fn new() -> Self {
KeepTopics {
current_set: SetId(0),
rounds: VecDeque::with_capacity(KEEP_RECENT_ROUNDS + 1),
reverse_map: HashMap::new(),
}
}
fn push(&mut self, round: Round, set_id: SetId) {
self.current_set = std::cmp::max(self.current_set, set_id);
self.rounds.push_back((round, set_id));
// the 1 is for the current round.
while self.rounds.len() > KEEP_RECENT_ROUNDS + 1 {
let _ = self.rounds.pop_front();
}
let mut map = HashMap::with_capacity(KEEP_RECENT_ROUNDS + 2);
map.insert(super::global_topic::(self.current_set.0), (None, self.current_set));
for &(round, set) in &self.rounds {
map.insert(
super::round_topic::(round.0, set.0),
(Some(round), set)
);
}
self.reverse_map = map;
}
fn topic_info(&self, topic: &B::Hash) -> Option<(Option, SetId)> {
self.reverse_map.get(topic).cloned()
}
}
// topics to send to a neighbor based on their view.
fn neighbor_topics(view: &View>) -> Vec {
let s = view.set_id;
let mut topics = vec![
super::global_topic::(s.0),
super::round_topic::(view.round.0, s.0),
];
if view.round.0 != 0 {
let r = Round(view.round.0 - 1);
topics.push(super::round_topic::(r.0, s.0))
}
topics
}
/// Grandpa gossip message type.
/// This is the root type that gets encoded and sent on the network.
#[derive(Debug, Encode, Decode)]
pub(super) enum GossipMessage {
/// Grandpa message with round and set info.
VoteOrPrecommit(VoteOrPrecommitMessage),
/// Grandpa commit message with round and set info.
Commit(FullCommitMessage),
/// A neighbor packet. Not repropagated.
Neighbor(VersionedNeighborPacket>),
}
impl From>> for GossipMessage {
fn from(neighbor: NeighborPacket>) -> Self {
GossipMessage::Neighbor(VersionedNeighborPacket::V1(neighbor))
}
}
/// Network level message with topic information.
#[derive(Debug, Encode, Decode)]
pub(super) struct VoteOrPrecommitMessage {
/// The round this message is from.
pub(super) round: Round,
/// The voter set ID this message is from.
pub(super) set_id: SetId,
/// The message itself.
pub(super) message: SignedMessage,
}
/// Network level commit message with topic information.
#[derive(Debug, Encode, Decode)]
pub(super) struct FullCommitMessage {
/// The round this message is from.
pub(super) round: Round,
/// The voter set ID this message is from.
pub(super) set_id: SetId,
/// The compact commit message.
pub(super) message: CompactCommit,
}
/// V1 neighbor packet. Neighbor packets are sent from nodes to their peers
/// and are not repropagated. These contain information about the node's state.
#[derive(Debug, Encode, Decode, Clone)]
pub(super) struct NeighborPacket {
round: Round,
set_id: SetId,
commit_finalized_height: N,
}
/// A versioned neighbor packet.
#[derive(Debug, Encode, Decode)]
pub(super) enum VersionedNeighborPacket {
#[codec(index = "1")]
V1(NeighborPacket),
}
impl VersionedNeighborPacket {
fn into_neighbor_packet(self) -> NeighborPacket {
match self {
VersionedNeighborPacket::V1(p) => p,
}
}
}
/// Misbehavior that peers can perform.
///
/// `cost` gives a cost that can be used to perform cost/benefit analysis of a
/// peer.
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) enum Misbehavior {
// invalid neighbor message, considering the last one.
InvalidViewChange,
// could not decode neighbor message. bytes-length of the packet.
UndecodablePacket(i32),
// Bad commit message
BadCommitMessage {
signatures_checked: i32,
blocks_loaded: i32,
equivocations_caught: i32,
},
// A message received that's from the future relative to our view.
// always misbehavior.
FutureMessage,
}
impl Misbehavior {
pub(super) fn cost(&self) -> i32 {
use Misbehavior::*;
match *self {
InvalidViewChange => cost::INVALID_VIEW_CHANGE,
UndecodablePacket(bytes) => bytes.saturating_mul(cost::PER_UNDECODABLE_BYTE),
BadCommitMessage { signatures_checked, blocks_loaded, equivocations_caught } => {
let cost = cost::PER_SIGNATURE_CHECKED
.saturating_mul(signatures_checked)
.saturating_add(cost::PER_BLOCK_LOADED.saturating_mul(blocks_loaded));
let benefit = equivocations_caught.saturating_mul(benefit::PER_EQUIVOCATION);
(benefit as i32).saturating_add(cost as i32)
},
FutureMessage => cost::FUTURE_MESSAGE,
}
}
}
struct PeerInfo {
view: View,
}
impl PeerInfo {
fn new() -> Self {
PeerInfo {
view: View::default(),
}
}
}
/// The peers we're connected do in gossip.
struct Peers {
inner: HashMap>,
}
impl Default for Peers {
fn default() -> Self {
Peers { inner: HashMap::new() }
}
}
impl Peers {
fn new_peer(&mut self, who: PeerId) {
self.inner.insert(who, PeerInfo::new());
}
fn peer_disconnected(&mut self, who: &PeerId) {
self.inner.remove(who);
}
// returns a reference to the new view, if the peer is known.
fn update_peer_state(&mut self, who: &PeerId, update: NeighborPacket)
-> Result