local_collations.rs 6.02 KB
Newer Older
Shawn Tabrizi's avatar
Shawn Tabrizi committed
1
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.

//! 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.

22
use polkadot_primitives::{Hash, parachain::{ValidatorId}};
23
use crate::collator_pool::Role;
24
use std::collections::{HashMap, HashSet};
Gavin Wood's avatar
Gavin Wood committed
25
26
use std::time::Duration;
use wasm_timer::Instant;
27
28
29
30

const LIVE_FOR: Duration = Duration::from_secs(60 * 5);

struct LocalCollation<C> {
31
	targets: HashSet<ValidatorId>,
32
33
34
35
36
37
	collation: C,
	live_since: Instant,
}

/// Tracker for locally collated values and which validators to send them to.
pub struct LocalCollations<C> {
38
	primary_for: HashSet<ValidatorId>,
39
40
41
42
43
44
45
46
47
48
49
50
51
52
	local_collations: HashMap<Hash, LocalCollation<C>>,
}

impl<C: Clone> LocalCollations<C> {
	/// 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.
53
	pub fn note_validator_role(&mut self, key: ValidatorId, role: Role) -> Vec<(Hash, C)> {
54
55
56
57
58
59
		match role {
			Role::Backup => {
				self.primary_for.remove(&key);
				Vec::new()
			}
			Role::Primary => {
Gav Wood's avatar
Gav Wood committed
60
				let new_primary = self.primary_for.insert(key.clone());
61
62
63
64
65
66
67
68
69
70
71
				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.
72
	pub fn fresh_key(&mut self, old_key: &ValidatorId, new_key: &ValidatorId) -> Vec<(Hash, C)> {
73
		if self.primary_for.remove(old_key) {
Gav Wood's avatar
Gav Wood committed
74
			self.primary_for.insert(new_key.clone());
75
76
77
78
79
80
81
82

			self.collations_targeting(new_key)
		} else {
			Vec::new()
		}
	}

	/// Validator disconnected.
83
	pub fn on_disconnect(&mut self, key: &ValidatorId) {
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
		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,
101
		targets: HashSet<ValidatorId>,
102
103
		collation: C
	)
104
		-> impl Iterator<Item=(ValidatorId, C)> + 'a
105
106
107
108
109
110
111
112
113
114
115
116
117
	{
		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;
		local.targets
			.intersection(&self.primary_for)
Gav Wood's avatar
Gav Wood committed
118
			.map(move |k| (k.clone(), borrowed_collation.clone()))
119
120
	}

121
	fn collations_targeting(&self, key: &ValidatorId) -> Vec<(Hash, C)> {
122
123
124
125
126
127
128
129
130
131
		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::*;
132
	use sp_core::crypto::UncheckedInto;
Gav Wood's avatar
Gav Wood committed
133
	use polkadot_primitives::parachain::ValidatorId;
134
135
136

	#[test]
	fn add_validator_with_ready_collation() {
Gav Wood's avatar
Gav Wood committed
137
		let key: ValidatorId = [1; 32].unchecked_into();
138
139
140
		let relay_parent = [2; 32].into();
		let targets = {
			let mut set = HashSet::new();
Gav Wood's avatar
Gav Wood committed
141
			set.insert(key.clone());
142
143
144
145
146
147
148
149
150
151
			set
		};

		let mut tracker = LocalCollations::new();
		assert!(tracker.add_collation(relay_parent, targets, 5).next().is_none());
		assert_eq!(tracker.note_validator_role(key, Role::Primary), vec![(relay_parent, 5)]);
	}

	#[test]
	fn rename_with_ready() {
Gav Wood's avatar
Gav Wood committed
152
153
		let orig_key: ValidatorId = [1; 32].unchecked_into();
		let new_key: ValidatorId  = [2; 32].unchecked_into();
154
155
156
		let relay_parent = [255; 32].into();
		let targets = {
			let mut set = HashSet::new();
Gav Wood's avatar
Gav Wood committed
157
			set.insert(new_key.clone());
158
159
160
161
162
			set
		};

		let mut tracker: LocalCollations<u8> = LocalCollations::new();
		assert!(tracker.add_collation(relay_parent, targets, 5).next().is_none());
Gav Wood's avatar
Gav Wood committed
163
		assert!(tracker.note_validator_role(orig_key.clone(), Role::Primary).is_empty());
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
		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<u8> = 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() {
Gav Wood's avatar
Gav Wood committed
187
		let key: ValidatorId = [1; 32].unchecked_into();
188
189
190
		let relay_parent = [2; 32].into();
		let targets = {
			let mut set = HashSet::new();
Gav Wood's avatar
Gav Wood committed
191
			set.insert(key.clone());
192
193
194
195
			set
		};

		let mut tracker = LocalCollations::new();
Gav Wood's avatar
Gav Wood committed
196
		assert!(tracker.note_validator_role(key.clone(), Role::Primary).is_empty());
197
198
199
200
		assert_eq!(tracker.add_collation(relay_parent, targets, 5).next(), Some((key, 5)));

	}
}