// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see .
use net_protocol::{filter_by_peer_version, peer_set::ProtocolVersion};
use parity_scale_codec::Encode;
use polkadot_node_network_protocol::{
self as net_protocol,
grid_topology::{GridNeighbors, RequiredRouting, SessionBoundGridTopologyStorage},
peer_set::{IsAuthority, PeerSet, ValidationVersion},
v1::{self as protocol_v1, StatementMetadata},
v2 as protocol_v2, v3 as protocol_v3, IfDisconnected, PeerId, UnifiedReputationChange as Rep,
Versioned, View,
};
use polkadot_node_primitives::{
SignedFullStatement, Statement, StatementWithPVD, UncheckedSignedFullStatement,
};
use polkadot_node_subsystem_util::{
self as util, rand, reputation::ReputationAggregator, MIN_GOSSIP_PEERS,
};
use polkadot_node_subsystem::{
jaeger,
messages::{CandidateBackingMessage, NetworkBridgeEvent, NetworkBridgeTxMessage},
overseer, ActivatedLeaf, PerLeafSpan, StatementDistributionSenderTrait,
};
use polkadot_primitives::{
AuthorityDiscoveryId, CandidateHash, CommittedCandidateReceipt, CompactStatement, Hash,
Id as ParaId, IndexedVec, OccupiedCoreAssumption, PersistedValidationData, SignedStatement,
SigningContext, UncheckedSignedStatement, ValidatorId, ValidatorIndex, ValidatorSignature,
};
use futures::{
channel::{mpsc, oneshot},
future::RemoteHandle,
prelude::*,
};
use indexmap::{map::Entry as IEntry, IndexMap};
use rand::Rng;
use sp_keystore::KeystorePtr;
use util::runtime::RuntimeInfo;
use std::collections::{hash_map::Entry, HashMap, HashSet, VecDeque};
use crate::error::{Error, JfyiError, JfyiErrorResult, Result};
/// Background task logic for requesting of large statements.
mod requester;
use requester::fetch;
/// Background task logic for responding for large statements.
mod responder;
use crate::{metrics::Metrics, LOG_TARGET};
pub use requester::RequesterMessage;
pub use responder::{respond, ResponderMessage};
#[cfg(test)]
mod tests;
const COST_UNEXPECTED_STATEMENT: Rep = Rep::CostMinor("Unexpected Statement");
const COST_UNEXPECTED_STATEMENT_MISSING_KNOWLEDGE: Rep =
Rep::CostMinor("Unexpected Statement, missing knowlege for relay parent");
const COST_UNEXPECTED_STATEMENT_UNKNOWN_CANDIDATE: Rep =
Rep::CostMinor("Unexpected Statement, unknown candidate");
const COST_UNEXPECTED_STATEMENT_REMOTE: Rep =
Rep::CostMinor("Unexpected Statement, remote not allowed");
const COST_FETCH_FAIL: Rep =
Rep::CostMinor("Requesting `CommittedCandidateReceipt` from peer failed");
const COST_INVALID_SIGNATURE: Rep = Rep::CostMajor("Invalid Statement Signature");
const COST_WRONG_HASH: Rep = Rep::CostMajor("Received candidate had wrong hash");
const COST_DUPLICATE_STATEMENT: Rep =
Rep::CostMajorRepeated("Statement sent more than once by peer");
const COST_APPARENT_FLOOD: Rep = Rep::Malicious("Peer appears to be flooding us with statements");
const BENEFIT_VALID_STATEMENT: Rep = Rep::BenefitMajor("Peer provided a valid statement");
const BENEFIT_VALID_STATEMENT_FIRST: Rep =
Rep::BenefitMajorFirst("Peer was the first to provide a valid statement");
const BENEFIT_VALID_RESPONSE: Rep =
Rep::BenefitMajor("Peer provided a valid large statement response");
/// The maximum amount of candidates each validator is allowed to second at any relay-parent.
/// Short for "Validator Candidate Threshold".
///
/// This is the amount of candidates we keep per validator at any relay-parent.
/// Typically we will only keep 1, but when a validator equivocates we will need to track 2.
const VC_THRESHOLD: usize = 2;
/// Large statements should be rare.
const MAX_LARGE_STATEMENTS_PER_SENDER: usize = 20;
/// Overall state of the legacy-v1 portion of the subsystem.
pub(crate) struct State {
peers: HashMap,
topology_storage: SessionBoundGridTopologyStorage,
authorities: HashMap,
active_heads: HashMap,
recent_outdated_heads: RecentOutdatedHeads,
runtime: RuntimeInfo,
}
impl State {
/// Create a new state.
pub(crate) fn new(keystore: KeystorePtr) -> Self {
State {
peers: HashMap::new(),
topology_storage: Default::default(),
authorities: HashMap::new(),
active_heads: HashMap::new(),
recent_outdated_heads: RecentOutdatedHeads::default(),
runtime: RuntimeInfo::new(Some(keystore)),
}
}
/// Query whether the state contains some relay-parent.
pub(crate) fn contains_relay_parent(&self, relay_parent: &Hash) -> bool {
self.active_heads.contains_key(relay_parent)
}
}
#[derive(Default)]
struct RecentOutdatedHeads {
buf: VecDeque,
}
impl RecentOutdatedHeads {
fn note_outdated(&mut self, hash: Hash) {
const MAX_BUF_LEN: usize = 10;
self.buf.push_back(hash);
while self.buf.len() > MAX_BUF_LEN {
let _ = self.buf.pop_front();
}
}
fn is_recent_outdated(&self, hash: &Hash) -> bool {
self.buf.contains(hash)
}
}
/// Tracks our impression of a single peer's view of the candidates a validator has seconded
/// for a given relay-parent.
///
/// It is expected to receive at most `VC_THRESHOLD` from us and be aware of at most `VC_THRESHOLD`
/// via other means.
#[derive(Default)]
struct VcPerPeerTracker {
local_observed: arrayvec::ArrayVec,
remote_observed: arrayvec::ArrayVec,
}
impl VcPerPeerTracker {
/// Note that the remote should now be aware that a validator has seconded a given candidate (by
/// hash) based on a message that we have sent it from our local pool.
fn note_local(&mut self, h: CandidateHash) {
if !note_hash(&mut self.local_observed, h) {
gum::warn!(
target: LOG_TARGET,
"Statement distribution is erroneously attempting to distribute more \
than {} candidate(s) per validator index. Ignoring",
VC_THRESHOLD,
);
}
}
/// Note that the remote should now be aware that a validator has seconded a given candidate (by
/// hash) based on a message that it has sent us.
///
/// Returns `true` if the peer was allowed to send us such a message, `false` otherwise.
fn note_remote(&mut self, h: CandidateHash) -> bool {
note_hash(&mut self.remote_observed, h)
}
/// Returns `true` if the peer is allowed to send us such a message, `false` otherwise.
fn is_wanted_candidate(&self, h: &CandidateHash) -> bool {
!self.remote_observed.contains(h) && !self.remote_observed.is_full()
}
}
fn note_hash(
observed: &mut arrayvec::ArrayVec,
h: CandidateHash,
) -> bool {
if observed.contains(&h) {
return true
}
observed.try_push(h).is_ok()
}
/// knowledge that a peer has about goings-on in a relay parent.
#[derive(Default)]
struct PeerRelayParentKnowledge {
/// candidates that the peer is aware of because we sent statements to it. This indicates that
/// we can send other statements pertaining to that candidate.
sent_candidates: HashSet,
/// candidates that peer is aware of, because we received statements from it.
received_candidates: HashSet,
/// fingerprints of all statements a peer should be aware of: those that
/// were sent to the peer by us.
sent_statements: HashSet<(CompactStatement, ValidatorIndex)>,
/// fingerprints of all statements a peer should be aware of: those that
/// were sent to us by the peer.
received_statements: HashSet<(CompactStatement, ValidatorIndex)>,
/// How many candidates this peer is aware of for each given validator index.
seconded_counts: HashMap,
/// How many statements we've received for each candidate that we're aware of.
received_message_count: HashMap,
/// How many large statements this peer already sent us.
///
/// Flood protection for large statements is rather hard and as soon as we get
/// `https://github.com/paritytech/polkadot/issues/2979` implemented also no longer necessary.
/// Reason: We keep messages around until we fetched the payload, but if a node makes up
/// statements and never provides the data, we will keep it around for the slot duration. Not
/// even signature checking would help, as the sender, if a validator, can just sign arbitrary
/// invalid statements and will not face any consequences as long as it won't provide the
/// payload.
///
/// Quick and temporary fix, only accept `MAX_LARGE_STATEMENTS_PER_SENDER` per connected node.
///
/// Large statements should be rare, if they were not, we would run into problems anyways, as
/// we would not be able to distribute them in a timely manner. Therefore
/// `MAX_LARGE_STATEMENTS_PER_SENDER` can be set to a relatively small number. It is also not
/// per candidate hash, but in total as candidate hashes can be made up, as illustrated above.
///
/// An attacker could still try to fill up our memory, by repeatedly disconnecting and
/// connecting again with new peer ids, but we assume that the resulting effective bandwidth
/// for such an attack would be too low.
large_statement_count: usize,
/// We have seen a message that that is unexpected from this peer, so note this fact
/// and stop subsequent logging and peer reputation flood.
unexpected_count: usize,
}
impl PeerRelayParentKnowledge {
/// Updates our view of the peer's knowledge with this statement's fingerprint based
/// on something that we would like to send to the peer.
///
/// NOTE: assumes `self.can_send` returned true before this call.
///
/// Once the knowledge has incorporated a statement, it cannot be incorporated again.
///
/// This returns `true` if this is the first time the peer has become aware of a
/// candidate with the given hash.
fn send(&mut self, fingerprint: &(CompactStatement, ValidatorIndex)) -> bool {
debug_assert!(
self.can_send(fingerprint),
"send is only called after `can_send` returns true; qed",
);
let new_known = match fingerprint.0 {
CompactStatement::Seconded(ref h) => {
self.seconded_counts.entry(fingerprint.1).or_default().note_local(*h);
let was_known = self.is_known_candidate(h);
self.sent_candidates.insert(*h);
!was_known
},
CompactStatement::Valid(_) => false,
};
self.sent_statements.insert(fingerprint.clone());
new_known
}
/// This returns `true` if the peer cannot accept this statement, without altering internal
/// state, `false` otherwise.
fn can_send(&self, fingerprint: &(CompactStatement, ValidatorIndex)) -> bool {
let already_known = self.sent_statements.contains(fingerprint) ||
self.received_statements.contains(fingerprint);
if already_known {
return false
}
match fingerprint.0 {
CompactStatement::Valid(ref h) => {
// The peer can only accept Valid statements for which it is aware
// of the corresponding candidate.
self.is_known_candidate(h)
},
CompactStatement::Seconded(_) => true,
}
}
/// Attempt to update our view of the peer's knowledge with this statement's fingerprint based
/// on a message we are receiving from the peer.
///
/// Provide the maximum message count that we can receive per candidate. In practice we should
/// not receive more statements for any one candidate than there are members in the group
/// assigned to that para, but this maximum needs to be lenient to account for equivocations
/// that may be cross-group. As such, a maximum of 2 * `n_validators` is recommended.
///
/// This returns an error if the peer should not have sent us this message according to protocol
/// rules for flood protection.
///
/// If this returns `Ok`, the internal state has been altered. After `receive`ing a new
/// candidate, we are then cleared to send the peer further statements about that candidate.
///
/// This returns `Ok(true)` if this is the first time the peer has become aware of a
/// candidate with given hash.
fn receive(
&mut self,
fingerprint: &(CompactStatement, ValidatorIndex),
max_message_count: usize,
) -> std::result::Result {
// We don't check `sent_statements` because a statement could be in-flight from both
// sides at the same time.
if self.received_statements.contains(fingerprint) {
return Err(COST_DUPLICATE_STATEMENT)
}
let (candidate_hash, fresh) = match fingerprint.0 {
CompactStatement::Seconded(ref h) => {
let allowed_remote = self
.seconded_counts
.entry(fingerprint.1)
.or_insert_with(Default::default)
.note_remote(*h);
if !allowed_remote {
return Err(COST_UNEXPECTED_STATEMENT_REMOTE)
}
(h, !self.is_known_candidate(h))
},
CompactStatement::Valid(ref h) => {
if !self.is_known_candidate(h) {
return Err(COST_UNEXPECTED_STATEMENT_UNKNOWN_CANDIDATE)
}
(h, false)
},
};
{
let received_per_candidate =
self.received_message_count.entry(*candidate_hash).or_insert(0);
if *received_per_candidate >= max_message_count {
return Err(COST_APPARENT_FLOOD)
}
*received_per_candidate += 1;
}
self.received_statements.insert(fingerprint.clone());
self.received_candidates.insert(*candidate_hash);
Ok(fresh)
}
/// Note a received large statement metadata.
fn receive_large_statement(&mut self) -> std::result::Result<(), Rep> {
if self.large_statement_count >= MAX_LARGE_STATEMENTS_PER_SENDER {
return Err(COST_APPARENT_FLOOD)
}
self.large_statement_count += 1;
Ok(())
}
/// This method does the same checks as `receive` without modifying the internal state.
/// Returns an error if the peer should not have sent us this message according to protocol
/// rules for flood protection.
fn check_can_receive(
&self,
fingerprint: &(CompactStatement, ValidatorIndex),
max_message_count: usize,
) -> std::result::Result<(), Rep> {
// We don't check `sent_statements` because a statement could be in-flight from both
// sides at the same time.
if self.received_statements.contains(fingerprint) {
return Err(COST_DUPLICATE_STATEMENT)
}
let candidate_hash = match fingerprint.0 {
CompactStatement::Seconded(ref h) => {
let allowed_remote = self
.seconded_counts
.get(&fingerprint.1)
.map_or(true, |r| r.is_wanted_candidate(h));
if !allowed_remote {
return Err(COST_UNEXPECTED_STATEMENT_REMOTE)
}
h
},
CompactStatement::Valid(ref h) => {
if !self.is_known_candidate(&h) {
return Err(COST_UNEXPECTED_STATEMENT_UNKNOWN_CANDIDATE)
}
h
},
};
let received_per_candidate = self.received_message_count.get(candidate_hash).unwrap_or(&0);
if *received_per_candidate >= max_message_count {
Err(COST_APPARENT_FLOOD)
} else {
Ok(())
}
}
/// Check for candidates that the peer is aware of. This indicates that we can
/// send other statements pertaining to that candidate.
fn is_known_candidate(&self, candidate: &CandidateHash) -> bool {
self.sent_candidates.contains(candidate) || self.received_candidates.contains(candidate)
}
}
pub struct PeerData {
view: View,
protocol_version: ValidationVersion,
view_knowledge: HashMap,
/// Peer might be known as authority with the given ids.
maybe_authority: Option>,
}
impl PeerData {
/// Updates our view of the peer's knowledge with this statement's fingerprint based
/// on something that we would like to send to the peer.
///
/// NOTE: assumes `self.can_send` returned true before this call.
///
/// Once the knowledge has incorporated a statement, it cannot be incorporated again.
///
/// This returns `true` if this is the first time the peer has become aware of a
/// candidate with the given hash.
fn send(
&mut self,
relay_parent: &Hash,
fingerprint: &(CompactStatement, ValidatorIndex),
) -> bool {
debug_assert!(
self.can_send(relay_parent, fingerprint),
"send is only called after `can_send` returns true; qed",
);
self.view_knowledge
.get_mut(relay_parent)
.expect("send is only called after `can_send` returns true; qed")
.send(fingerprint)
}
/// This returns `None` if the peer cannot accept this statement, without altering internal
/// state.
fn can_send(
&self,
relay_parent: &Hash,
fingerprint: &(CompactStatement, ValidatorIndex),
) -> bool {
self.view_knowledge.get(relay_parent).map_or(false, |k| k.can_send(fingerprint))
}
/// Attempt to update our view of the peer's knowledge with this statement's fingerprint based
/// on a message we are receiving from the peer.
///
/// Provide the maximum message count that we can receive per candidate. In practice we should
/// not receive more statements for any one candidate than there are members in the group
/// assigned to that para, but this maximum needs to be lenient to account for equivocations
/// that may be cross-group. As such, a maximum of 2 * `n_validators` is recommended.
///
/// This returns an error if the peer should not have sent us this message according to protocol
/// rules for flood protection.
///
/// If this returns `Ok`, the internal state has been altered. After `receive`ing a new
/// candidate, we are then cleared to send the peer further statements about that candidate.
///
/// This returns `Ok(true)` if this is the first time the peer has become aware of a
/// candidate with given hash.
fn receive(
&mut self,
relay_parent: &Hash,
fingerprint: &(CompactStatement, ValidatorIndex),
max_message_count: usize,
) -> std::result::Result {
self.view_knowledge
.get_mut(relay_parent)
.ok_or(COST_UNEXPECTED_STATEMENT_MISSING_KNOWLEDGE)?
.receive(fingerprint, max_message_count)
}
/// This method does the same checks as `receive` without modifying the internal state.
/// Returns an error if the peer should not have sent us this message according to protocol
/// rules for flood protection.
fn check_can_receive(
&self,
relay_parent: &Hash,
fingerprint: &(CompactStatement, ValidatorIndex),
max_message_count: usize,
) -> std::result::Result<(), Rep> {
self.view_knowledge
.get(relay_parent)
.ok_or(COST_UNEXPECTED_STATEMENT_MISSING_KNOWLEDGE)?
.check_can_receive(fingerprint, max_message_count)
}
/// Receive a notice about out of view statement and returns the value of the old flag
fn receive_unexpected(&mut self, relay_parent: &Hash) -> usize {
self.view_knowledge
.get_mut(relay_parent)
.map_or(0_usize, |relay_parent_peer_knowledge| {
let old = relay_parent_peer_knowledge.unexpected_count;
relay_parent_peer_knowledge.unexpected_count += 1_usize;
old
})
}
/// Basic flood protection for large statements.
fn receive_large_statement(&mut self, relay_parent: &Hash) -> std::result::Result<(), Rep> {
self.view_knowledge
.get_mut(relay_parent)
.ok_or(COST_UNEXPECTED_STATEMENT_MISSING_KNOWLEDGE)?
.receive_large_statement()
}
}
// A statement stored while a relay chain head is active.
#[derive(Debug, Copy, Clone)]
struct StoredStatement<'a> {
comparator: &'a StoredStatementComparator,
statement: &'a SignedFullStatement,
}
// A value used for comparison of stored statements to each other.
//
// The compact version of the statement, the validator index, and the signature of the validator
// is enough to differentiate between all types of equivocations, as long as the signature is
// actually checked to be valid. The same statement with 2 signatures and 2 statements with
// different (or same) signatures wll all be correctly judged to be unequal with this comparator.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
struct StoredStatementComparator {
compact: CompactStatement,
validator_index: ValidatorIndex,
signature: ValidatorSignature,
}
impl<'a> From<(&'a StoredStatementComparator, &'a SignedFullStatement)> for StoredStatement<'a> {
fn from(
(comparator, statement): (&'a StoredStatementComparator, &'a SignedFullStatement),
) -> Self {
Self { comparator, statement }
}
}
impl<'a> StoredStatement<'a> {
fn compact(&self) -> &'a CompactStatement {
&self.comparator.compact
}
fn fingerprint(&self) -> (CompactStatement, ValidatorIndex) {
(self.comparator.compact.clone(), self.statement.validator_index())
}
}
#[derive(Debug)]
enum NotedStatement<'a> {
NotUseful,
Fresh(StoredStatement<'a>),
UsefulButKnown,
}
/// Large statement fetching status.
enum LargeStatementStatus {
/// We are currently fetching the statement data from a remote peer. We keep a list of other
/// nodes claiming to have that data and will fallback on them.
Fetching(FetchingInfo),
/// Statement data is fetched or we got it locally via `StatementDistributionMessage::Share`.
FetchedOrShared(CommittedCandidateReceipt),
}
/// Info about a fetch in progress.
struct FetchingInfo {
/// All peers that send us a `LargeStatement` or a `Valid` statement for the given
/// `CandidateHash`, together with their originally sent messages.
///
/// We use an `IndexMap` here to preserve the ordering of peers sending us messages. This is
/// desirable because we reward first sending peers with reputation.
available_peers: IndexMap>,
/// Peers left to try in case the background task needs it.
peers_to_try: Vec,
/// Sender for sending fresh peers to the fetching task in case of failure.
peer_sender: Option>>,
/// Task taking care of the request.
///
/// Will be killed once dropped.
#[allow(dead_code)]
fetching_task: RemoteHandle<()>,
}
#[derive(Debug, PartialEq, Eq)]
enum DeniedStatement {
NotUseful,
UsefulButKnown,
}
pub(crate) struct ActiveHeadData {
/// All candidates we are aware of for this head, keyed by hash.
candidates: HashSet,
/// Persisted validation data cache.
cached_validation_data: HashMap,
/// Stored statements for circulation to peers.
///
/// These are iterable in insertion order, and `Seconded` statements are always
/// accepted before dependent statements.
statements: IndexMap,
/// Large statements we are waiting for with associated meta data.
waiting_large_statements: HashMap,
/// The parachain validators at the head's child session index.
validators: IndexedVec,
/// The current session index of this fork.
session_index: sp_staking::SessionIndex,
/// How many `Seconded` statements we've seen per validator.
seconded_counts: HashMap,
/// A Jaeger span for this head, so we can attach data to it.
span: PerLeafSpan,
}
impl ActiveHeadData {
fn new(
validators: IndexedVec,
session_index: sp_staking::SessionIndex,
span: PerLeafSpan,
) -> Self {
ActiveHeadData {
candidates: Default::default(),
cached_validation_data: Default::default(),
statements: Default::default(),
waiting_large_statements: Default::default(),
validators,
session_index,
seconded_counts: Default::default(),
span,
}
}
/// Fetches the `PersistedValidationData` from the runtime, assuming
/// that the core is free. The relay parent must match that of the active
/// head.
async fn fetch_persisted_validation_data(
&mut self,
sender: &mut Sender,
relay_parent: Hash,
para_id: ParaId,
) -> Result