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