local_collations.rs 5.99 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Copyright 2018 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 <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.

use polkadot_primitives::{Hash, SessionKey};

24
use crate::collator_pool::Role;
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

use std::collections::{HashMap, HashSet};
use std::time::{Duration, Instant};

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

struct LocalCollation<C> {
	targets: HashSet<SessionKey>,
	collation: C,
	live_since: Instant,
}

/// Tracker for locally collated values and which validators to send them to.
pub struct LocalCollations<C> {
	primary_for: HashSet<SessionKey>,
	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.
	pub fn note_validator_role(&mut self, key: SessionKey, role: Role) -> Vec<(Hash, C)> {
		match role {
			Role::Backup => {
				self.primary_for.remove(&key);
				Vec::new()
			}
			Role::Primary => {
Gav Wood's avatar
Gav Wood committed
61
				let new_primary = self.primary_for.insert(key.clone());
62
63
64
65
66
67
68
69
70
71
72
73
74
				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: &SessionKey, new_key: &SessionKey) -> Vec<(Hash, C)> {
		if self.primary_for.remove(old_key) {
Gav Wood's avatar
Gav Wood committed
75
			self.primary_for.insert(new_key.clone());
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

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

	/// Validator disconnected.
	pub fn on_disconnect(&mut self, key: &SessionKey) {
		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<SessionKey>,
		collation: C
	)
		-> impl Iterator<Item=(SessionKey, C)> + '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;
		local.targets
			.intersection(&self.primary_for)
Gav Wood's avatar
Gav Wood committed
119
			.map(move |k| (k.clone(), borrowed_collation.clone()))
120
121
122
123
124
125
126
127
128
129
130
131
132
	}

	fn collations_targeting(&self, key: &SessionKey) -> 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::*;
Gav Wood's avatar
Gav Wood committed
133
134
	use substrate_primitives::crypto::UncheckedInto;
	use polkadot_primitives::parachain::ValidatorId;
135
136
137

	#[test]
	fn add_validator_with_ready_collation() {
Gav Wood's avatar
Gav Wood committed
138
		let key: ValidatorId = [1; 32].unchecked_into();
139
140
141
		let relay_parent = [2; 32].into();
		let targets = {
			let mut set = HashSet::new();
Gav Wood's avatar
Gav Wood committed
142
			set.insert(key.clone());
143
144
145
146
147
148
149
150
151
152
			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
153
154
		let orig_key: ValidatorId = [1; 32].unchecked_into();
		let new_key: ValidatorId  = [2; 32].unchecked_into();
155
156
157
		let relay_parent = [255; 32].into();
		let targets = {
			let mut set = HashSet::new();
Gav Wood's avatar
Gav Wood committed
158
			set.insert(new_key.clone());
159
160
161
162
163
			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
164
		assert!(tracker.note_validator_role(orig_key.clone(), Role::Primary).is_empty());
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
		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
188
		let key: ValidatorId = [1; 32].unchecked_into();
189
190
191
		let relay_parent = [2; 32].into();
		let targets = {
			let mut set = HashSet::new();
Gav Wood's avatar
Gav Wood committed
192
			set.insert(key.clone());
193
194
195
196
			set
		};

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

	}
}