referrerpolicy=no-referrer-when-downgrade

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/// Maximum allowed incoming connection streams for validator nodes on the collation protocol.
43pub const MAX_AUTHORITY_INCOMING_STREAMS: u32 = 310;
44
45/// The peer-sets and thus the protocols which are used for the network.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
47pub enum PeerSet {
48	/// The validation peer-set is responsible for all messages related to candidate validation and
49	/// communication among validators.
50	Validation,
51	/// The collation peer-set is used for validator<>collator communication.
52	Collation,
53}
54
55/// Whether a node is an authority or not.
56///
57/// Peer set configuration gets adjusted accordingly.
58#[derive(Copy, Clone, Debug, Eq, PartialEq)]
59pub enum IsAuthority {
60	/// Node is authority.
61	Yes,
62	/// Node is not an authority.
63	No,
64}
65
66impl PeerSet {
67	/// Get `sc_network` peer set configurations for each peerset on the default version.
68	///
69	/// Those should be used in the network configuration to register the protocols with the
70	/// network service.
71	pub fn get_info<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
72		self,
73		is_authority: IsAuthority,
74		peerset_protocol_names: &PeerSetProtocolNames,
75		metrics: NotificationMetrics,
76		peer_store_handle: Arc<dyn PeerStoreProvider>,
77	) -> (N::NotificationProtocolConfig, (PeerSet, Box<dyn NotificationService>)) {
78		// Networking layer relies on `get_main_name()` being the main name of the protocol
79		// for peersets and connection management.
80		let protocol = peerset_protocol_names.get_main_name(self);
81		let fallback_names = peerset_protocol_names.get_fallback_names(self);
82		let max_notification_size = self.get_max_notification_size(is_authority);
83
84		match self {
85			PeerSet::Validation => {
86				let (config, notification_service) = N::notification_config(
87					protocol,
88					fallback_names,
89					max_notification_size,
90					None,
91					SetConfig {
92						// we allow full nodes to connect to validators for gossip
93						// to ensure any `MIN_GOSSIP_PEERS` always include reserved peers
94						// we limit the amount of non-reserved slots to be less
95						// than `MIN_GOSSIP_PEERS` in total
96						in_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1,
97						out_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1,
98						reserved_nodes: Vec::new(),
99						non_reserved_mode: sc_network::config::NonReservedPeerMode::Accept,
100					},
101					metrics,
102					peer_store_handle,
103				);
104
105				(config, (PeerSet::Validation, notification_service))
106			},
107			PeerSet::Collation => {
108				let (config, notification_service) = N::notification_config(
109					protocol,
110					fallback_names,
111					max_notification_size,
112					None,
113					SetConfig {
114						// Non-authority nodes don't need to accept incoming connections on this
115						// peer set:
116						in_peers: if is_authority == IsAuthority::Yes {
117							MAX_AUTHORITY_INCOMING_STREAMS
118						} else {
119							0
120						},
121						out_peers: 0,
122						reserved_nodes: Vec::new(),
123						non_reserved_mode: if is_authority == IsAuthority::Yes {
124							sc_network::config::NonReservedPeerMode::Accept
125						} else {
126							sc_network::config::NonReservedPeerMode::Deny
127						},
128					},
129					metrics,
130					peer_store_handle,
131				);
132
133				(config, (PeerSet::Collation, notification_service))
134			},
135		}
136	}
137
138	/// Get the max notification size for this peer set.
139	pub fn get_max_notification_size(self, _: IsAuthority) -> u64 {
140		MAX_NOTIFICATION_SIZE
141	}
142
143	/// Get the peer set label for metrics reporting.
144	pub fn get_label(self) -> &'static str {
145		match self {
146			PeerSet::Validation => "validation",
147			PeerSet::Collation => "collation",
148		}
149	}
150
151	/// Get the protocol label for metrics reporting.
152	pub fn get_protocol_label(self, version: ProtocolVersion) -> Option<&'static str> {
153		// Unfortunately, labels must be static strings, so we must manually cover them
154		// for all protocol versions here.
155		match self {
156			PeerSet::Validation => {
157				if version == ValidationVersion::V3.into() {
158					Some("validation/3")
159				} else {
160					None
161				}
162			},
163			PeerSet::Collation => {
164				if version == CollationVersion::V1.into() {
165					Some("collation/1")
166				} else if version == CollationVersion::V2.into() {
167					Some("collation/2")
168				} else if version == CollationVersion::V3.into() {
169					Some("collation/3")
170				} else if version == CollationVersion::V4.into() {
171					Some("collation/4")
172				} else {
173					None
174				}
175			},
176		}
177	}
178}
179
180/// A small and nifty collection that allows to store data pertaining to each peer set.
181#[derive(Debug, Default)]
182pub struct PerPeerSet<T> {
183	validation: T,
184	collation: T,
185}
186
187impl<T> Index<PeerSet> for PerPeerSet<T> {
188	type Output = T;
189	fn index(&self, index: PeerSet) -> &T {
190		match index {
191			PeerSet::Validation => &self.validation,
192			PeerSet::Collation => &self.collation,
193		}
194	}
195}
196
197impl<T> IndexMut<PeerSet> for PerPeerSet<T> {
198	fn index_mut(&mut self, index: PeerSet) -> &mut T {
199		match index {
200			PeerSet::Validation => &mut self.validation,
201			PeerSet::Collation => &mut self.collation,
202		}
203	}
204}
205
206/// Get `NonDefaultSetConfig`s for all available peer sets, at their default versions.
207///
208/// Should be used during network configuration (added to `NetworkConfiguration::extra_sets`)
209/// or shortly after startup to register the protocols with the network service.
210pub fn peer_sets_info<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
211	is_authority: IsAuthority,
212	peerset_protocol_names: &PeerSetProtocolNames,
213	metrics: NotificationMetrics,
214	peer_store_handle: Arc<dyn PeerStoreProvider>,
215) -> Vec<(N::NotificationProtocolConfig, (PeerSet, Box<dyn NotificationService>))> {
216	PeerSet::iter()
217		.map(|s| {
218			s.get_info::<B, N>(
219				is_authority,
220				&peerset_protocol_names,
221				metrics.clone(),
222				Arc::clone(&peer_store_handle),
223			)
224		})
225		.collect()
226}
227
228/// A generic version of the protocol. This struct must not be created directly.
229#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash)]
230pub struct ProtocolVersion(u32);
231
232impl From<ProtocolVersion> for u32 {
233	fn from(version: ProtocolVersion) -> u32 {
234		version.0
235	}
236}
237
238/// Supported validation protocol versions. Only versions defined here must be used in the codebase.
239#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
240pub enum ValidationVersion {
241	/// The third version.
242	V3 = 3,
243}
244
245/// Supported collation protocol versions. Only versions defined here must be used in the codebase.
246#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
247pub enum CollationVersion {
248	/// The first version.
249	V1 = 1,
250	/// The second version.
251	V2 = 2,
252	/// The third version, adds explicit scheduling_parent field and candidate descriptor version.
253	V3 = 3,
254	/// Adds possibility to advertise a list of collations.
255	V4 = 4,
256}
257
258/// Marker indicating the version is unknown.
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub struct UnknownVersion;
261
262impl TryFrom<ProtocolVersion> for ValidationVersion {
263	type Error = UnknownVersion;
264
265	fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
266		for v in Self::iter() {
267			if v as u32 == p.0 {
268				return Ok(v);
269			}
270		}
271
272		Err(UnknownVersion)
273	}
274}
275
276impl TryFrom<ProtocolVersion> for CollationVersion {
277	type Error = UnknownVersion;
278
279	fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
280		for v in Self::iter() {
281			if v as u32 == p.0 {
282				return Ok(v);
283			}
284		}
285
286		Err(UnknownVersion)
287	}
288}
289
290impl From<ValidationVersion> for ProtocolVersion {
291	fn from(version: ValidationVersion) -> ProtocolVersion {
292		ProtocolVersion(version as u32)
293	}
294}
295
296impl From<CollationVersion> for ProtocolVersion {
297	fn from(version: CollationVersion) -> ProtocolVersion {
298		ProtocolVersion(version as u32)
299	}
300}
301
302/// On the wire protocol name to [`PeerSet`] mapping.
303#[derive(Debug, Clone)]
304pub struct PeerSetProtocolNames {
305	protocols: HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
306	names: HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
307	genesis_hash: Hash,
308	fork_id: Option<String>,
309	main_collation_version: Option<CollationVersion>,
310}
311
312impl PeerSetProtocolNames {
313	/// Construct [`PeerSetProtocolNames`] using `genesis_hash` and `fork_id`.
314	pub fn new(genesis_hash: Hash, fork_id: Option<&str>) -> Self {
315		Self::new_with_main_collation_version(genesis_hash, fork_id, None)
316	}
317
318	/// Same as [`Self::new`], but pins the main collation protocol version.
319	///
320	/// With `None`, tracks the newest supported version — same as [`Self::new`].
321	///
322	/// Only intended for the legacy collator protocol, which is capped at
323	/// [`CollationVersion::V3`] and will never support newer versions.
324	pub fn new_with_main_collation_version(
325		genesis_hash: Hash,
326		fork_id: Option<&str>,
327		main_collation_version: Option<CollationVersion>,
328	) -> Self {
329		let mut protocols = HashMap::new();
330		let mut names = HashMap::new();
331		for protocol in PeerSet::iter() {
332			match protocol {
333				PeerSet::Validation => {
334					for version in ValidationVersion::iter() {
335						Self::register_main_protocol(
336							&mut protocols,
337							&mut names,
338							protocol,
339							version.into(),
340							&genesis_hash,
341							fork_id,
342						);
343					}
344				},
345				PeerSet::Collation => {
346					for version in CollationVersion::iter() {
347						Self::register_main_protocol(
348							&mut protocols,
349							&mut names,
350							protocol,
351							version.into(),
352							&genesis_hash,
353							fork_id,
354						);
355					}
356					Self::register_legacy_collation_protocol(&mut protocols, protocol);
357				},
358			}
359		}
360		Self {
361			protocols,
362			names,
363			genesis_hash,
364			fork_id: fork_id.map(|fork_id| fork_id.into()),
365			main_collation_version,
366		}
367	}
368
369	/// Helper function to register main protocol.
370	fn register_main_protocol(
371		protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
372		names: &mut HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
373		protocol: PeerSet,
374		version: ProtocolVersion,
375		genesis_hash: &Hash,
376		fork_id: Option<&str>,
377	) {
378		let protocol_name = Self::generate_name(genesis_hash, fork_id, protocol, version);
379		names.insert((protocol, version), protocol_name.clone());
380		Self::insert_protocol_or_panic(protocols, protocol_name, protocol, version);
381	}
382
383	/// Helper function to register legacy collation protocol.
384	fn register_legacy_collation_protocol(
385		protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
386		protocol: PeerSet,
387	) {
388		Self::insert_protocol_or_panic(
389			protocols,
390			LEGACY_COLLATION_PROTOCOL_V1.into(),
391			protocol,
392			ProtocolVersion(LEGACY_COLLATION_PROTOCOL_VERSION_V1),
393		)
394	}
395
396	/// Helper function to make sure no protocols have the same name.
397	fn insert_protocol_or_panic(
398		protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
399		name: ProtocolName,
400		protocol: PeerSet,
401		version: ProtocolVersion,
402	) {
403		match protocols.entry(name) {
404			Entry::Vacant(entry) => {
405				entry.insert((protocol, version));
406			},
407			Entry::Occupied(entry) => {
408				panic!(
409					"Protocol {:?} (version {}) has the same on-the-wire name as protocol {:?} (version {}): `{}`.",
410					protocol,
411					version,
412					entry.get().0,
413					entry.get().1,
414					entry.key(),
415				);
416			},
417		}
418	}
419
420	/// Lookup the protocol using its on the wire name.
421	pub fn try_get_protocol(&self, name: &ProtocolName) -> Option<(PeerSet, ProtocolVersion)> {
422		self.protocols.get(name).map(ToOwned::to_owned)
423	}
424
425	/// Get the main protocol name. It's used by the networking for keeping track
426	/// of peersets and connections.
427	pub fn get_main_name(&self, protocol: PeerSet) -> ProtocolName {
428		self.get_name(protocol, self.get_main_version(protocol))
429	}
430
431	/// Get the protocol name for specific version.
432	pub fn get_name(&self, protocol: PeerSet, version: ProtocolVersion) -> ProtocolName {
433		self.names
434			.get(&(protocol, version))
435			.expect("Protocols & versions are specified via enums defined above, and they are all registered in `new()`; qed")
436			.clone()
437	}
438
439	/// The protocol name of this protocol based on `genesis_hash` and `fork_id`.
440	fn generate_name(
441		genesis_hash: &Hash,
442		fork_id: Option<&str>,
443		protocol: PeerSet,
444		version: ProtocolVersion,
445	) -> ProtocolName {
446		let prefix = if let Some(fork_id) = fork_id {
447			format!("/{}/{}", hex::encode(genesis_hash), fork_id)
448		} else {
449			format!("/{}", hex::encode(genesis_hash))
450		};
451
452		let short_name = match protocol {
453			PeerSet::Validation => "validation",
454			PeerSet::Collation => "collation",
455		};
456
457		format!("{}/{}/{}", prefix, short_name, version).into()
458	}
459
460	/// Get the protocol fallback names for negotiation with older peers.
461	pub fn get_fallback_names(&self, protocol: PeerSet) -> Vec<ProtocolName> {
462		let mut fallbacks = vec![];
463		match protocol {
464			PeerSet::Validation => {
465				// The validation protocol no longer supports protocol versions 1 and 2,
466				// and only version 3 is used. Therefore, fallback protocols remain empty.
467			},
468			PeerSet::Collation => {
469				// Collation V3 fallback so that V4 nodes can negotiate V3 with older peers
470				if self.get_main_version(PeerSet::Collation) == CollationVersion::V4.into() {
471					fallbacks.push(Self::generate_name(
472						&self.genesis_hash,
473						self.fork_id.as_deref(),
474						PeerSet::Collation,
475						CollationVersion::V3.into(),
476					));
477				}
478				// Collation V2 fallback so that V4 nodes can negotiate V2 with older peers
479				// instead of falling all the way back to the legacy V1 protocol.
480				fallbacks.push(Self::generate_name(
481					&self.genesis_hash,
482					self.fork_id.as_deref(),
483					PeerSet::Collation,
484					CollationVersion::V2.into(),
485				));
486				fallbacks.push(LEGACY_COLLATION_PROTOCOL_V1.into());
487			},
488		};
489		fallbacks
490	}
491
492	/// Networking layer relies on this being the version of the main
493	/// protocol name reported by `get_main_name()`.
494	pub fn get_main_version(&self, protocol: PeerSet) -> ProtocolVersion {
495		match protocol {
496			PeerSet::Validation => Self::newest_validation_version().into(),
497			PeerSet::Collation => self
498				.main_collation_version
499				.unwrap_or_else(Self::newest_collation_version)
500				.into(),
501		}
502	}
503
504	fn newest_collation_version() -> CollationVersion {
505		CollationVersion::iter()
506			.max_by_key(|v| *v as u32)
507			.expect("`CollationVersion` has at least one variant; qed")
508	}
509
510	fn newest_validation_version() -> ValidationVersion {
511		ValidationVersion::iter()
512			.max_by_key(|v| *v as u32)
513			.expect("ValidationVersion` has at least one variant; qed")
514	}
515}
516
517#[cfg(test)]
518mod tests {
519	use super::{
520		CollationVersion, Hash, PeerSet, PeerSetProtocolNames, ProtocolVersion, ValidationVersion,
521	};
522	use strum::IntoEnumIterator;
523
524	struct TestVersion(u32);
525
526	impl From<TestVersion> for ProtocolVersion {
527		fn from(version: TestVersion) -> ProtocolVersion {
528			ProtocolVersion(version.0)
529		}
530	}
531
532	#[test]
533	fn protocol_names_are_correctly_generated() {
534		let genesis_hash = Hash::from([
535			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
536			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
537		]);
538		let name = PeerSetProtocolNames::generate_name(
539			&genesis_hash,
540			None,
541			PeerSet::Validation,
542			TestVersion(3).into(),
543		);
544		let expected =
545			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/validation/3";
546		assert_eq!(name, expected.into());
547
548		let name = PeerSetProtocolNames::generate_name(
549			&genesis_hash,
550			None,
551			PeerSet::Collation,
552			TestVersion(5).into(),
553		);
554		let expected =
555			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/collation/5";
556		assert_eq!(name, expected.into());
557
558		let fork_id = Some("test-fork");
559		let name = PeerSetProtocolNames::generate_name(
560			&genesis_hash,
561			fork_id,
562			PeerSet::Validation,
563			TestVersion(7).into(),
564		);
565		let expected =
566			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/test-fork/validation/7";
567		assert_eq!(name, expected.into());
568
569		let name = PeerSetProtocolNames::generate_name(
570			&genesis_hash,
571			fork_id,
572			PeerSet::Collation,
573			TestVersion(11).into(),
574		);
575		let expected =
576			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/test-fork/collation/11";
577		assert_eq!(name, expected.into());
578	}
579
580	#[test]
581	fn all_protocol_names_are_known() {
582		let genesis_hash = Hash::from([
583			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
584			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
585		]);
586		let protocol_names = PeerSetProtocolNames::new(genesis_hash, None);
587
588		let validation_main =
589			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/validation/3";
590		assert_eq!(
591			protocol_names.try_get_protocol(&validation_main.into()),
592			Some((PeerSet::Validation, TestVersion(3).into())),
593		);
594
595		let validation_legacy = "/polkadot/validation/1";
596		assert!(protocol_names.try_get_protocol(&validation_legacy.into()).is_none());
597
598		let collation_main =
599			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/collation/1";
600		assert_eq!(
601			protocol_names.try_get_protocol(&collation_main.into()),
602			Some((PeerSet::Collation, TestVersion(1).into())),
603		);
604
605		let collation_legacy = "/polkadot/collation/1";
606		assert_eq!(
607			protocol_names.try_get_protocol(&collation_legacy.into()),
608			Some((PeerSet::Collation, TestVersion(1).into())),
609		);
610	}
611
612	#[test]
613	fn all_protocol_versions_are_registered() {
614		let genesis_hash = Hash::from([
615			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
616			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
617		]);
618		let protocol_names = PeerSetProtocolNames::new(genesis_hash, None);
619
620		for protocol in PeerSet::iter() {
621			match protocol {
622				PeerSet::Validation => {
623					for version in ValidationVersion::iter() {
624						assert_eq!(
625							protocol_names.get_name(protocol, version.into()),
626							PeerSetProtocolNames::generate_name(
627								&genesis_hash,
628								None,
629								protocol,
630								version.into(),
631							),
632						);
633					}
634				},
635				PeerSet::Collation => {
636					for version in CollationVersion::iter() {
637						assert_eq!(
638							protocol_names.get_name(protocol, version.into()),
639							PeerSetProtocolNames::generate_name(
640								&genesis_hash,
641								None,
642								protocol,
643								version.into(),
644							),
645						);
646					}
647				},
648			}
649		}
650	}
651
652	/// Asserts that every version of a peer set is reachable via the main protocol name
653	/// or a fallback. Without this, bumping the main version without adding the old one
654	/// as a fallback causes peers to silently downgrade further than intended.
655	fn assert_all_versions_negotiable(
656		peer_set: PeerSet,
657		main_collation_version: Option<CollationVersion>,
658		versions: impl Iterator<Item = ProtocolVersion>,
659	) {
660		let genesis_hash = Hash::from([
661			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
662			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
663		]);
664		let protocol_names = PeerSetProtocolNames::new_with_main_collation_version(
665			genesis_hash,
666			None,
667			main_collation_version,
668		);
669		let main_version = protocol_names.get_main_version(peer_set);
670		let fallback_names = protocol_names.get_fallback_names(peer_set);
671
672		// Collect versions reachable via main + fallbacks.
673		let mut negotiable_versions: std::collections::HashSet<ProtocolVersion> =
674			std::collections::HashSet::new();
675		negotiable_versions.insert(main_version);
676		for fallback in &fallback_names {
677			if let Some((ps, version)) = protocol_names.try_get_protocol(fallback) {
678				assert_eq!(ps, peer_set);
679				negotiable_versions.insert(version);
680			}
681		}
682
683		for version in versions {
684			assert!(
685				negotiable_versions.contains(&version),
686				"{:?} version {} is not negotiable: \
687				 not the main version ({}) and not reachable via any fallback name ({:?}). \
688				 Add it to get_fallback_names().",
689				peer_set,
690				version,
691				main_version,
692				fallback_names,
693			);
694		}
695	}
696
697	#[test]
698	fn all_collation_versions_are_negotiable() {
699		assert_all_versions_negotiable(
700			PeerSet::Collation,
701			None,
702			CollationVersion::iter().map(Into::into),
703		);
704	}
705
706	#[test]
707	fn all_validation_versions_are_negotiable() {
708		assert_all_versions_negotiable(
709			PeerSet::Validation,
710			None,
711			ValidationVersion::iter().map(Into::into),
712		);
713	}
714
715	#[test]
716	fn all_protocol_versions_have_labels() {
717		for protocol in PeerSet::iter() {
718			match protocol {
719				PeerSet::Validation => {
720					for version in ValidationVersion::iter() {
721						protocol
722							.get_protocol_label(version.into())
723							.expect("All validation protocol versions must have a label.");
724					}
725				},
726				PeerSet::Collation => {
727					for version in CollationVersion::iter() {
728						protocol
729							.get_protocol_label(version.into())
730							.expect("All collation protocol versions must have a label.");
731					}
732				},
733			}
734		}
735	}
736
737	#[test]
738	fn v3_main_caps_collation_at_v3() {
739		// Everything up to V3 stays negotiable...
740		assert_all_versions_negotiable(
741			PeerSet::Collation,
742			Some(CollationVersion::V3),
743			CollationVersion::iter().filter(|v| *v != CollationVersion::V4).map(Into::into),
744		);
745
746		// ...and V4 is not reachable at all: not the main name, not a fallback.
747		let genesis_hash = Hash::from([
748			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
749			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
750		]);
751		let protocol_names = PeerSetProtocolNames::new_with_main_collation_version(
752			genesis_hash,
753			None,
754			Some(CollationVersion::V3),
755		);
756		let v4_name = protocol_names.get_name(PeerSet::Collation, CollationVersion::V4.into());
757		let main_name = protocol_names.get_main_name(PeerSet::Collation);
758		let fallbacks = protocol_names.get_fallback_names(PeerSet::Collation);
759
760		assert_ne!(main_name, v4_name);
761		assert!(!fallbacks.contains(&v4_name));
762		// The regression test for the duplicate-/3 bug: main never appears in fallbacks.
763		assert!(!fallbacks.contains(&main_name));
764	}
765}