polkadot_node_network_protocol/
peer_set.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! All peersets and protocols used for parachains.
18
19use derive_more::Display;
20use polkadot_primitives::Hash;
21use sc_network::{
22	config::SetConfig, peer_store::PeerStoreProvider, service::NotificationMetrics,
23	types::ProtocolName, NetworkBackend, NotificationService,
24};
25use sp_runtime::traits::Block;
26use std::{
27	collections::{hash_map::Entry, HashMap},
28	ops::{Index, IndexMut},
29	sync::Arc,
30};
31use strum::{EnumIter, IntoEnumIterator};
32
33/// The legacy collation protocol name. Only supported on version = 1.
34const LEGACY_COLLATION_PROTOCOL_V1: &str = "/polkadot/collation/1";
35
36/// The legacy protocol version. Is always 1 for collation.
37const LEGACY_COLLATION_PROTOCOL_VERSION_V1: u32 = 1;
38
39/// Max notification size is currently constant.
40pub const MAX_NOTIFICATION_SIZE: u64 = 100 * 1024;
41
42/// The peer-sets and thus the protocols which are used for the network.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
44pub enum PeerSet {
45	/// The validation peer-set is responsible for all messages related to candidate validation and
46	/// communication among validators.
47	Validation,
48	/// The collation peer-set is used for validator<>collator communication.
49	Collation,
50}
51
52/// Whether a node is an authority or not.
53///
54/// Peer set configuration gets adjusted accordingly.
55#[derive(Copy, Clone, Debug, Eq, PartialEq)]
56pub enum IsAuthority {
57	/// Node is authority.
58	Yes,
59	/// Node is not an authority.
60	No,
61}
62
63impl PeerSet {
64	/// Get `sc_network` peer set configurations for each peerset on the default version.
65	///
66	/// Those should be used in the network configuration to register the protocols with the
67	/// network service.
68	pub fn get_info<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
69		self,
70		is_authority: IsAuthority,
71		peerset_protocol_names: &PeerSetProtocolNames,
72		metrics: NotificationMetrics,
73		peer_store_handle: Arc<dyn PeerStoreProvider>,
74	) -> (N::NotificationProtocolConfig, (PeerSet, Box<dyn NotificationService>)) {
75		// Networking layer relies on `get_main_name()` being the main name of the protocol
76		// for peersets and connection management.
77		let protocol = peerset_protocol_names.get_main_name(self);
78		let fallback_names = PeerSetProtocolNames::get_fallback_names(
79			self,
80			&peerset_protocol_names.genesis_hash,
81			peerset_protocol_names.fork_id.as_deref(),
82		);
83		let max_notification_size = self.get_max_notification_size(is_authority);
84
85		match self {
86			PeerSet::Validation => {
87				let (config, notification_service) = N::notification_config(
88					protocol,
89					fallback_names,
90					max_notification_size,
91					None,
92					SetConfig {
93						// we allow full nodes to connect to validators for gossip
94						// to ensure any `MIN_GOSSIP_PEERS` always include reserved peers
95						// we limit the amount of non-reserved slots to be less
96						// than `MIN_GOSSIP_PEERS` in total
97						in_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1,
98						out_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1,
99						reserved_nodes: Vec::new(),
100						non_reserved_mode: sc_network::config::NonReservedPeerMode::Accept,
101					},
102					metrics,
103					peer_store_handle,
104				);
105
106				(config, (PeerSet::Validation, notification_service))
107			},
108			PeerSet::Collation => {
109				let (config, notification_service) = N::notification_config(
110					protocol,
111					fallback_names,
112					max_notification_size,
113					None,
114					SetConfig {
115						// Non-authority nodes don't need to accept incoming connections on this
116						// peer set:
117						in_peers: if is_authority == IsAuthority::Yes { 100 } else { 0 },
118						out_peers: 0,
119						reserved_nodes: Vec::new(),
120						non_reserved_mode: if is_authority == IsAuthority::Yes {
121							sc_network::config::NonReservedPeerMode::Accept
122						} else {
123							sc_network::config::NonReservedPeerMode::Deny
124						},
125					},
126					metrics,
127					peer_store_handle,
128				);
129
130				(config, (PeerSet::Collation, notification_service))
131			},
132		}
133	}
134
135	/// Get the main protocol version for this peer set.
136	///
137	/// Networking layer relies on `get_main_version()` being the version
138	/// of the main protocol name reported by [`PeerSetProtocolNames::get_main_name()`].
139	pub fn get_main_version(self) -> ProtocolVersion {
140		match self {
141			PeerSet::Validation => ValidationVersion::V3.into(),
142			PeerSet::Collation => CollationVersion::V2.into(),
143		}
144	}
145
146	/// Get the max notification size for this peer set.
147	pub fn get_max_notification_size(self, _: IsAuthority) -> u64 {
148		MAX_NOTIFICATION_SIZE
149	}
150
151	/// Get the peer set label for metrics reporting.
152	pub fn get_label(self) -> &'static str {
153		match self {
154			PeerSet::Validation => "validation",
155			PeerSet::Collation => "collation",
156		}
157	}
158
159	/// Get the protocol label for metrics reporting.
160	pub fn get_protocol_label(self, version: ProtocolVersion) -> Option<&'static str> {
161		// Unfortunately, labels must be static strings, so we must manually cover them
162		// for all protocol versions here.
163		match self {
164			PeerSet::Validation =>
165				if version == ValidationVersion::V3.into() {
166					Some("validation/3")
167				} else {
168					None
169				},
170			PeerSet::Collation =>
171				if version == CollationVersion::V1.into() {
172					Some("collation/1")
173				} else if version == CollationVersion::V2.into() {
174					Some("collation/2")
175				} else {
176					None
177				},
178		}
179	}
180}
181
182/// A small and nifty collection that allows to store data pertaining to each peer set.
183#[derive(Debug, Default)]
184pub struct PerPeerSet<T> {
185	validation: T,
186	collation: T,
187}
188
189impl<T> Index<PeerSet> for PerPeerSet<T> {
190	type Output = T;
191	fn index(&self, index: PeerSet) -> &T {
192		match index {
193			PeerSet::Validation => &self.validation,
194			PeerSet::Collation => &self.collation,
195		}
196	}
197}
198
199impl<T> IndexMut<PeerSet> for PerPeerSet<T> {
200	fn index_mut(&mut self, index: PeerSet) -> &mut T {
201		match index {
202			PeerSet::Validation => &mut self.validation,
203			PeerSet::Collation => &mut self.collation,
204		}
205	}
206}
207
208/// Get `NonDefaultSetConfig`s for all available peer sets, at their default versions.
209///
210/// Should be used during network configuration (added to `NetworkConfiguration::extra_sets`)
211/// or shortly after startup to register the protocols with the network service.
212pub fn peer_sets_info<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
213	is_authority: IsAuthority,
214	peerset_protocol_names: &PeerSetProtocolNames,
215	metrics: NotificationMetrics,
216	peer_store_handle: Arc<dyn PeerStoreProvider>,
217) -> Vec<(N::NotificationProtocolConfig, (PeerSet, Box<dyn NotificationService>))> {
218	PeerSet::iter()
219		.map(|s| {
220			s.get_info::<B, N>(
221				is_authority,
222				&peerset_protocol_names,
223				metrics.clone(),
224				Arc::clone(&peer_store_handle),
225			)
226		})
227		.collect()
228}
229
230/// A generic version of the protocol. This struct must not be created directly.
231#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash)]
232pub struct ProtocolVersion(u32);
233
234impl From<ProtocolVersion> for u32 {
235	fn from(version: ProtocolVersion) -> u32 {
236		version.0
237	}
238}
239
240/// Supported validation protocol versions. Only versions defined here must be used in the codebase.
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
242pub enum ValidationVersion {
243	/// The third version.
244	V3 = 3,
245}
246
247/// Supported collation protocol versions. Only versions defined here must be used in the codebase.
248#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
249pub enum CollationVersion {
250	/// The first version.
251	V1 = 1,
252	/// The second version.
253	V2 = 2,
254}
255
256/// Marker indicating the version is unknown.
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub struct UnknownVersion;
259
260impl TryFrom<ProtocolVersion> for ValidationVersion {
261	type Error = UnknownVersion;
262
263	fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
264		for v in Self::iter() {
265			if v as u32 == p.0 {
266				return Ok(v)
267			}
268		}
269
270		Err(UnknownVersion)
271	}
272}
273
274impl TryFrom<ProtocolVersion> for CollationVersion {
275	type Error = UnknownVersion;
276
277	fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
278		for v in Self::iter() {
279			if v as u32 == p.0 {
280				return Ok(v)
281			}
282		}
283
284		Err(UnknownVersion)
285	}
286}
287
288impl From<ValidationVersion> for ProtocolVersion {
289	fn from(version: ValidationVersion) -> ProtocolVersion {
290		ProtocolVersion(version as u32)
291	}
292}
293
294impl From<CollationVersion> for ProtocolVersion {
295	fn from(version: CollationVersion) -> ProtocolVersion {
296		ProtocolVersion(version as u32)
297	}
298}
299
300/// On the wire protocol name to [`PeerSet`] mapping.
301#[derive(Debug, Clone)]
302pub struct PeerSetProtocolNames {
303	protocols: HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
304	names: HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
305	genesis_hash: Hash,
306	fork_id: Option<String>,
307}
308
309impl PeerSetProtocolNames {
310	/// Construct [`PeerSetProtocolNames`] using `genesis_hash` and `fork_id`.
311	pub fn new(genesis_hash: Hash, fork_id: Option<&str>) -> Self {
312		let mut protocols = HashMap::new();
313		let mut names = HashMap::new();
314		for protocol in PeerSet::iter() {
315			match protocol {
316				PeerSet::Validation =>
317					for version in ValidationVersion::iter() {
318						Self::register_main_protocol(
319							&mut protocols,
320							&mut names,
321							protocol,
322							version.into(),
323							&genesis_hash,
324							fork_id,
325						);
326					},
327				PeerSet::Collation => {
328					for version in CollationVersion::iter() {
329						Self::register_main_protocol(
330							&mut protocols,
331							&mut names,
332							protocol,
333							version.into(),
334							&genesis_hash,
335							fork_id,
336						);
337					}
338					Self::register_legacy_collation_protocol(&mut protocols, protocol);
339				},
340			}
341		}
342		Self { protocols, names, genesis_hash, fork_id: fork_id.map(|fork_id| fork_id.into()) }
343	}
344
345	/// Helper function to register main protocol.
346	fn register_main_protocol(
347		protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
348		names: &mut HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
349		protocol: PeerSet,
350		version: ProtocolVersion,
351		genesis_hash: &Hash,
352		fork_id: Option<&str>,
353	) {
354		let protocol_name = Self::generate_name(genesis_hash, fork_id, protocol, version);
355		names.insert((protocol, version), protocol_name.clone());
356		Self::insert_protocol_or_panic(protocols, protocol_name, protocol, version);
357	}
358
359	/// Helper function to register legacy collation protocol.
360	fn register_legacy_collation_protocol(
361		protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
362		protocol: PeerSet,
363	) {
364		Self::insert_protocol_or_panic(
365			protocols,
366			LEGACY_COLLATION_PROTOCOL_V1.into(),
367			protocol,
368			ProtocolVersion(LEGACY_COLLATION_PROTOCOL_VERSION_V1),
369		)
370	}
371
372	/// Helper function to make sure no protocols have the same name.
373	fn insert_protocol_or_panic(
374		protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
375		name: ProtocolName,
376		protocol: PeerSet,
377		version: ProtocolVersion,
378	) {
379		match protocols.entry(name) {
380			Entry::Vacant(entry) => {
381				entry.insert((protocol, version));
382			},
383			Entry::Occupied(entry) => {
384				panic!(
385					"Protocol {:?} (version {}) has the same on-the-wire name as protocol {:?} (version {}): `{}`.",
386					protocol,
387					version,
388					entry.get().0,
389					entry.get().1,
390					entry.key(),
391				);
392			},
393		}
394	}
395
396	/// Lookup the protocol using its on the wire name.
397	pub fn try_get_protocol(&self, name: &ProtocolName) -> Option<(PeerSet, ProtocolVersion)> {
398		self.protocols.get(name).map(ToOwned::to_owned)
399	}
400
401	/// Get the main protocol name. It's used by the networking for keeping track
402	/// of peersets and connections.
403	pub fn get_main_name(&self, protocol: PeerSet) -> ProtocolName {
404		self.get_name(protocol, protocol.get_main_version())
405	}
406
407	/// Get the protocol name for specific version.
408	pub fn get_name(&self, protocol: PeerSet, version: ProtocolVersion) -> ProtocolName {
409		self.names
410			.get(&(protocol, version))
411			.expect("Protocols & versions are specified via enums defined above, and they are all registered in `new()`; qed")
412			.clone()
413	}
414
415	/// The protocol name of this protocol based on `genesis_hash` and `fork_id`.
416	fn generate_name(
417		genesis_hash: &Hash,
418		fork_id: Option<&str>,
419		protocol: PeerSet,
420		version: ProtocolVersion,
421	) -> ProtocolName {
422		let prefix = if let Some(fork_id) = fork_id {
423			format!("/{}/{}", hex::encode(genesis_hash), fork_id)
424		} else {
425			format!("/{}", hex::encode(genesis_hash))
426		};
427
428		let short_name = match protocol {
429			PeerSet::Validation => "validation",
430			PeerSet::Collation => "collation",
431		};
432
433		format!("{}/{}/{}", prefix, short_name, version).into()
434	}
435
436	/// Get the protocol fallback names. Currently, it only holds
437	/// the legacy name for the collation protocol version 1.
438	fn get_fallback_names(
439		protocol: PeerSet,
440		_genesis_hash: &Hash,
441		_fork_id: Option<&str>,
442	) -> Vec<ProtocolName> {
443		let mut fallbacks = vec![];
444		match protocol {
445			PeerSet::Validation => {
446				// The validation protocol no longer supports protocol versions 1 and 2,
447				// and only version 3 is used. Therefore, fallback protocols remain empty.
448			},
449			PeerSet::Collation => {
450				fallbacks.push(LEGACY_COLLATION_PROTOCOL_V1.into());
451			},
452		};
453		fallbacks
454	}
455}
456
457#[cfg(test)]
458mod tests {
459	use super::{
460		CollationVersion, Hash, PeerSet, PeerSetProtocolNames, ProtocolVersion, ValidationVersion,
461	};
462	use strum::IntoEnumIterator;
463
464	struct TestVersion(u32);
465
466	impl From<TestVersion> for ProtocolVersion {
467		fn from(version: TestVersion) -> ProtocolVersion {
468			ProtocolVersion(version.0)
469		}
470	}
471
472	#[test]
473	fn protocol_names_are_correctly_generated() {
474		let genesis_hash = Hash::from([
475			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
476			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
477		]);
478		let name = PeerSetProtocolNames::generate_name(
479			&genesis_hash,
480			None,
481			PeerSet::Validation,
482			TestVersion(3).into(),
483		);
484		let expected =
485			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/validation/3";
486		assert_eq!(name, expected.into());
487
488		let name = PeerSetProtocolNames::generate_name(
489			&genesis_hash,
490			None,
491			PeerSet::Collation,
492			TestVersion(5).into(),
493		);
494		let expected =
495			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/collation/5";
496		assert_eq!(name, expected.into());
497
498		let fork_id = Some("test-fork");
499		let name = PeerSetProtocolNames::generate_name(
500			&genesis_hash,
501			fork_id,
502			PeerSet::Validation,
503			TestVersion(7).into(),
504		);
505		let expected =
506			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/test-fork/validation/7";
507		assert_eq!(name, expected.into());
508
509		let name = PeerSetProtocolNames::generate_name(
510			&genesis_hash,
511			fork_id,
512			PeerSet::Collation,
513			TestVersion(11).into(),
514		);
515		let expected =
516			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/test-fork/collation/11";
517		assert_eq!(name, expected.into());
518	}
519
520	#[test]
521	fn all_protocol_names_are_known() {
522		let genesis_hash = Hash::from([
523			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
524			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
525		]);
526		let protocol_names = PeerSetProtocolNames::new(genesis_hash, None);
527
528		let validation_main =
529			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/validation/3";
530		assert_eq!(
531			protocol_names.try_get_protocol(&validation_main.into()),
532			Some((PeerSet::Validation, TestVersion(3).into())),
533		);
534
535		let validation_legacy = "/polkadot/validation/1";
536		assert!(protocol_names.try_get_protocol(&validation_legacy.into()).is_none());
537
538		let collation_main =
539			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/collation/1";
540		assert_eq!(
541			protocol_names.try_get_protocol(&collation_main.into()),
542			Some((PeerSet::Collation, TestVersion(1).into())),
543		);
544
545		let collation_legacy = "/polkadot/collation/1";
546		assert_eq!(
547			protocol_names.try_get_protocol(&collation_legacy.into()),
548			Some((PeerSet::Collation, TestVersion(1).into())),
549		);
550	}
551
552	#[test]
553	fn all_protocol_versions_are_registered() {
554		let genesis_hash = Hash::from([
555			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
556			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
557		]);
558		let protocol_names = PeerSetProtocolNames::new(genesis_hash, None);
559
560		for protocol in PeerSet::iter() {
561			match protocol {
562				PeerSet::Validation =>
563					for version in ValidationVersion::iter() {
564						assert_eq!(
565							protocol_names.get_name(protocol, version.into()),
566							PeerSetProtocolNames::generate_name(
567								&genesis_hash,
568								None,
569								protocol,
570								version.into(),
571							),
572						);
573					},
574				PeerSet::Collation =>
575					for version in CollationVersion::iter() {
576						assert_eq!(
577							protocol_names.get_name(protocol, version.into()),
578							PeerSetProtocolNames::generate_name(
579								&genesis_hash,
580								None,
581								protocol,
582								version.into(),
583							),
584						);
585					},
586			}
587		}
588	}
589
590	#[test]
591	fn all_protocol_versions_have_labels() {
592		for protocol in PeerSet::iter() {
593			match protocol {
594				PeerSet::Validation =>
595					for version in ValidationVersion::iter() {
596						protocol
597							.get_protocol_label(version.into())
598							.expect("All validation protocol versions must have a label.");
599					},
600				PeerSet::Collation =>
601					for version in CollationVersion::iter() {
602						protocol
603							.get_protocol_label(version.into())
604							.expect("All collation protocol versions must have a label.");
605					},
606			}
607		}
608	}
609}