// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see .
//! Local collations to be circulated to validators.
//!
//! Collations are attempted to be repropagated when a new validator connects,
//! a validator changes his session key, or when they are generated.
use polkadot_primitives::{Hash, parachain::{ValidatorId}};
use crate::legacy::collator_pool::Role;
use std::collections::{HashMap, HashSet};
use std::time::Duration;
use wasm_timer::Instant;
use rand::seq::SliceRandom;
const LIVE_FOR: Duration = Duration::from_secs(60 * 5);
struct LocalCollation {
targets: HashSet,
collation: C,
live_since: Instant,
}
/// Tracker for locally collated values and which validators to send them to.
pub struct LocalCollations {
primary_for: HashSet,
local_collations: HashMap>,
}
impl Default for LocalCollations {
fn default() -> Self {
Self::new()
}
}
impl LocalCollations {
/// Create a new `LocalCollations` tracker.
pub fn new() -> Self {
LocalCollations {
primary_for: HashSet::new(),
local_collations: HashMap::new(),
}
}
/// Validator gave us a new role. If the new role is "primary", this function might return
/// a set of collations to send to that validator.
pub fn note_validator_role(&mut self, key: ValidatorId, role: Role) -> Vec<(Hash, C)> {
match role {
Role::Backup => {
self.primary_for.remove(&key);
Vec::new()
}
Role::Primary => {
let new_primary = self.primary_for.insert(key.clone());
if new_primary {
self.collations_targeting(&key)
} else {
Vec::new()
}
}
}
}
/// Fresh session key from a validator. Returns a vector of collations to send
/// to the validator.
pub fn fresh_key(&mut self, old_key: &ValidatorId, new_key: &ValidatorId) -> Vec<(Hash, C)> {
if self.primary_for.remove(old_key) {
self.primary_for.insert(new_key.clone());
self.collations_targeting(new_key)
} else {
Vec::new()
}
}
/// Validator disconnected.
pub fn on_disconnect(&mut self, key: &ValidatorId) {
self.primary_for.remove(key);
}
/// Mark collations relevant to the given parent hash as obsolete.
pub fn collect_garbage(&mut self, relay_parent: Option<&Hash>) {
if let Some(relay_parent) = relay_parent {
self.local_collations.remove(relay_parent);
}
let now = Instant::now();
self.local_collations.retain(|_, v| v.live_since + LIVE_FOR > now);
}
/// Add a collation. Returns an iterator of session keys to send to and lazy copies of the collation.
pub fn add_collation<'a>(
&'a mut self,
relay_parent: Hash,
targets: HashSet,
collation: C
) -> impl Iterator- + 'a {
self.local_collations.insert(relay_parent, LocalCollation {
targets,
collation,
live_since: Instant::now(),
});
let local = self.local_collations.get(&relay_parent)
.expect("just inserted to this key; qed");
let borrowed_collation = &local.collation;
// If we are conected to multiple validators,
// make sure we always send the collation to one of the validators
// we are registered as backup. This ensures that one collator that
// is primary at multiple validators, desn't block the Parachain from progressing.
let mut rng = rand::thread_rng();
let diff = local.targets.difference(&self.primary_for).collect::>();
local.targets
.intersection(&self.primary_for)
.chain(diff.choose(&mut rng).map(|r| r.clone()))
.map(move |k| (k.clone(), borrowed_collation.clone()))
}
fn collations_targeting(&self, key: &ValidatorId) -> Vec<(Hash, C)> {
self.local_collations.iter()
.filter(|&(_, ref v)| v.targets.contains(key))
.map(|(h, v)| (*h, v.collation.clone()))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_core::crypto::UncheckedInto;
use polkadot_primitives::parachain::ValidatorId;
#[test]
fn add_validator_with_ready_collation() {
let key: ValidatorId = [1; 32].unchecked_into();
let relay_parent = [2; 32].into();
let targets = {
let mut set = HashSet::new();
set.insert(key.clone());
set
};
let mut tracker = LocalCollations::new();
assert!(tracker.add_collation(relay_parent, targets, 5).next().is_some());
assert_eq!(tracker.note_validator_role(key, Role::Primary), vec![(relay_parent, 5)]);
}
#[test]
fn rename_with_ready() {
let orig_key: ValidatorId = [1; 32].unchecked_into();
let new_key: ValidatorId = [2; 32].unchecked_into();
let relay_parent = [255; 32].into();
let targets = {
let mut set = HashSet::new();
set.insert(new_key.clone());
set
};
let mut tracker: LocalCollations = LocalCollations::new();
assert!(tracker.add_collation(relay_parent, targets, 5).next().is_some());
assert!(tracker.note_validator_role(orig_key.clone(), Role::Primary).is_empty());
assert_eq!(tracker.fresh_key(&orig_key, &new_key), vec![(relay_parent, 5u8)]);
}
#[test]
fn collecting_garbage() {
let relay_parent_a = [255; 32].into();
let relay_parent_b = [222; 32].into();
let mut tracker: LocalCollations = LocalCollations::new();
assert!(tracker.add_collation(relay_parent_a, HashSet::new(), 5).next().is_none());
assert!(tracker.add_collation(relay_parent_b, HashSet::new(), 69).next().is_none());
let live_since = Instant::now() - LIVE_FOR - Duration::from_secs(10);
tracker.local_collations.get_mut(&relay_parent_b).unwrap().live_since = live_since;
tracker.collect_garbage(Some(&relay_parent_a));
// first one pruned because of relay parent, other because of time.
assert!(tracker.local_collations.is_empty());
}
#[test]
fn add_collation_with_connected_target() {
let key: ValidatorId = [1; 32].unchecked_into();
let relay_parent = [2; 32].into();
let targets = {
let mut set = HashSet::new();
set.insert(key.clone());
set
};
let mut tracker = LocalCollations::new();
assert!(tracker.note_validator_role(key.clone(), Role::Primary).is_empty());
assert_eq!(tracker.add_collation(relay_parent, targets, 5).next(), Some((key, 5)));
}
}